Table of Contents

Class CreateTableCommand

Namespace
DynaSharp
Assembly
DynaSharp.dll

Creates a table from what its entities declare: await orders.CreateTable().Including<Customer>().Including<Product>().SendAsync(ct);. CreateTable() starts one.

public sealed class CreateTableCommand
Inheritance
CreateTableCommand
Inherited Members

Remarks

Key names, index names, key schemas and attribute types come from the metadata of the entities. Billing is on demand unless Provisioned(long, long) says otherwise, and every index projects all attributes. Local and global indexes are both created. A local index exists only if the table is created with it, so an entity left out here means an index that cannot be added later; a global one can, through UpdateTable in DynaSharp.Requests.

One entity does not describe a single table, whose indexes are the union over its entities, so Including<TOther>() names the others. A table without an entity starts from the key names it was opened with, as strings, unless an included entity stores them otherwise.

A key template is a string, and a plain key is what its converter writes: a number, a binary value, or a string for anything else. A custom converter says what it writes only by implementing IAddableConverter, which makes the key a number; any other counts as a string, so a custom converter that writes binary needs ToRequest(). Stream(StreamViewType) and TimeToLive(string) cover those two. For tags, a storage class or a projection of fewer attributes, take ToRequest() and send it through DynaSharp.Requests.

Unlike the commands on items, this is an ordinary class: a clause changes the command and returns it, it can be kept in a field and sent again, and it is not thread-safe. A retry repeats the request: when a response was lost, the retry of a table this very call created fails with ResourceInUseException, which IfNotExists() turns into success.

Methods

IfNotExists()

Treats a table that exists as success: DescribeTable goes first, and a table it finds ends the create there, with its description and without a CreateTable, which is what makes a create at the start of a program safe to repeat. The table it finds is checked against what the command declares. TimeToLive(string) on the chain still goes on, and can change the table.

public CreateTableCommand IfNotExists()

Returns

CreateTableCommand

This command.

Remarks

What it checks. The description it already has holds the schema, so the check takes no call. A key of the table that differs from the key of the command throws an InvalidOperationException that names the table, the key it expects and the key it found, before any wait or time to live: another partition or sort key attribute, a sort key the command has and the table lacks or the other way round, or a key attribute of another type where both sides know it. That table fails every read and write of the entities with a ValidationException, and DynamoDB cannot change the key of a table, so a deploy should stop there. A table opened without an entity, client.Table(name, "pk", "sk"), knows its key names and not their types, so only the names count.

What lets part of the code work is no exception, but Drift, which the command also logs once as a warning: an index the entities declare that the table lacks, or has with other keys or as the other kind, time to live on another attribute than the one asked for, and, with Stream(StreamViewType) on the chain, a stream that is off or of another view. The command changes none of it, as the indexes and the stream of a command reach only a table it creates: a global index goes in with UpdateTable, and a local one only with a new table. What the table has beyond the command, such as an index or time to live it does not ask for, is no drift, since other code may use it. A table that is still being created is success with that status. A WaitUntilActive() still fails over a table that lacks a global index this command declares. Created says whether the table was found.

Permissions. A table that exists takes dynamodb:DescribeTable alone, unless TimeToLive(string) adds its own, so a role that may not create tables gets through, and no failed call shows in CloudTrail or in error metrics. Creating a table takes dynamodb:CreateTable as well, and dynamodb:DescribeTable is needed for that too: the look comes first, and any error of it other than a missing table surfaces, so a role without it gets AccessDeniedException and creates nothing. With WaitUntilActive(), a first look that already shows the table and its indexes active returns without another call.

A table that is going away is no success, with or without a wait, because no table is left afterwards: the command goes on to CreateTable, and when DescribeTable says DELETING again, the ResourceInUseException of the create surfaces, and sending the command again creates the table once the delete is through.

When DescribeTable finds no table, the command creates it. That can still find a table, because DescribeTable is eventually consistent, and instances of a program that start together create at the same time: ResourceInUseException then ends in the description of the table it found. When DescribeTable finds nothing after such an answer, either a delete just ended or another process just created the table, so the command creates again, up to 5 times over 1.5 seconds, which ends in a new table or in the one that turned up. The ResourceInUseException surfaces only if that settles nothing.

Including<TOther>()

Adds what another entity on this table declares, so that its indexes exist too. The result is the union: an index two entities declare exists once, and several templates over the same attributes, an overloaded index, are one index. Including an entity twice changes nothing.

public CreateTableCommand Including<TOther>() where TOther : IDynamoDbEntity<TOther>

Returns

CreateTableCommand

This command.

Type Parameters

TOther

Any entity on this table.

Exceptions

ArgumentException

The entity disagrees with the table or with an entity before it, which the message names both of: the key of the table has other names, it stores an attribute as another type, it declares an index of that name over other attributes, or as local where the other is global, or its TimeToLiveAttribute is on another attribute, since a table has time to live on one.

Provisioned(long, long)

Gives the table provisioned capacity instead of on-demand billing. DynamoDB needs capacity for each global index as well, and each gets these numbers; a local index shares that of the table. For other numbers per index, take ToRequest().

public CreateTableCommand Provisioned(long read, long write)

Parameters

read long

The read capacity units per second, at least 1.

write long

The write capacity units per second, at least 1.

Returns

CreateTableCommand

This command.

SendAsync(CancellationToken)

Creates the table (CreateTable). Creation is asynchronous: without WaitUntilActive() or time to live, of TimeToLive(string) or of a TimeToLiveAttribute of an entity, which implies it, the table is normally CREATING when this returns.

public ValueTask<CreateTableResult> SendAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

Cancels the call, and the wait. A table DynamoDB accepted stays accepted, so a cancelled wait leaves a table behind.

Returns

ValueTask<CreateTableResult>

The table and what this call did: Table is the new description, the active one with WaitUntilActive(), or the existing one with IfNotExists(), which is what the first look found; Created and TimeToLiveEnabled say whether this call created the table and turned time to live on, and Drift how a table it found differs from what it declares.

Exceptions

ObjectDisposedException

The client is disposed.

ResourceInUseException

A table of that name exists and IfNotExists() was not called, or it is being deleted, which IfNotExists() does not cover.

AccessDeniedException

The role lacks a permission of the calls this makes, which IfNotExists() lists.

LimitExceededException

The account has too many tables, or creates or deletes tables too fast.

ValidationException

DynamoDB rejects the request, for example because the name is malformed or the table has too many indexes, or time to live cannot be turned on, as within the hour after a change of it.

InvalidOperationException

IfNotExists() found a table whose key differs from the key of the command, in attribute names, in having a sort key or in a type that both know. The message names the table, the expected and the found key. Nothing waits and nothing changes then.

TableNotActiveException

The table or an index of it was not active in time, with WaitUntilActive() or TimeToLive(string), or the table did not list a global index this command declares, which ends the wait early.

TimeoutException

The command ran out of Timeout(TimeSpan), with a message that names the table, the timeout and the step, and the OperationCanceledException of the call in flight inside it. Or a call ran out of AttemptTimeout on its last attempt.

DynamoDbException

DynamoDB returns any other error; a throttling or server error only after the last attempt.

Stream(StreamViewType)

Gives the table a stream, which ToRequest() shows. It applies to a table this command creates: with IfNotExists(), the stream of a table that exists stays untouched, and one that is off or of another view is a StreamDiffers in Drift. Change it with UpdateTable in DynaSharp.Requests.

public CreateTableCommand Stream(StreamViewType view)

Parameters

view StreamViewType

What a record of the stream holds.

Returns

CreateTableCommand

This command.

Exceptions

ArgumentOutOfRangeException

view is no view, which DynamoDB would refuse, and this call finds out before a table is created.

TimeToLive(Attr)

Makes sure that the table has time to live on the attribute of a handle: .TimeToLive(Order.Attrs.ExpiresAt). See TimeToLive(string).

public CreateTableCommand TimeToLive(Attr attribute)

Parameters

attribute Attr

A handle, or an Attr of a name.

Returns

CreateTableCommand

This command.

Exceptions

ArgumentException

attribute has no path, or a nested one, because time to live reads an attribute at the top of an item.

TimeToLive(string)

Makes sure that the table has time to live on this attribute, on a table this command creates and on one IfNotExists() finds. CreateTable does not take it, so it is no part of ToRequest(): it happens after the create, or after finding the table. An entity of the command with TimeToLiveAttribute asks for the same without this call, and this call replaces what the entities declare.

public CreateTableCommand TimeToLive(string attributeName)

Parameters

attributeName string

The name of the attribute that holds the expiry time.

Returns

CreateTableCommand

This command.

Remarks

It needs an active table, so it implies WaitUntilActive(), with the five minutes of that method unless WaitUntilActive(TimeSpan) gave another timeout, and with all its rules: a wait that runs out throws TableNotActiveException and leaves time to live as it was, also when a table IfNotExists() found lacks a global index this command declares. So a program that adds an index to a table that exists calls this after it added the index.

Then DescribeTimeToLive looks, and ENABLED or ENABLING on this attribute ends it. ENABLED or ENABLING on another attribute ends it too, and changes nothing: it is a TimeToLiveDiffers in Drift, since turning it off and on again with the new name is a decision this command does not take, and one that DynamoDB spreads over two changes. Anything else, time to live off for one, sends UpdateTimeToLive, which turns it on, on a table that exists too. DynamoDB answers that with a ValidationException for time to live that is on already, and for a second change within the hour that a change takes. Instances of a program start together, so that can mean that another one got there first: the command looks again and takes it for success if time to live is now on, for this attribute or, as drift, for another. Otherwise that exception surfaces. TimeToLiveEnabled is true only after an update DynamoDB accepted.

Permissions. It takes dynamodb:DescribeTimeToLive on every call, dynamodb:UpdateTimeToLive when time to live is not on yet, and dynamodb:DescribeTable for the wait.

The attribute of an item must be a number that holds a Unix time in seconds, which [DynamoDbConverter(typeof(EpochSecondsConverter))] on a DateTimeOffset writes, and which TimeToLiveAttribute checks at build time. See UpdateTimeToLiveRequest for when DynamoDB deletes.

Exceptions

ArgumentException

attributeName is null or empty.

Timeout(TimeSpan)

Bounds the whole of SendAsync(CancellationToken), every call it makes and every wait, for a program that creates its table at the start and must not hang there. When the time runs out, SendAsync(CancellationToken) throws a TimeoutException that names the table, the timeout and the step it was at.

public CreateTableCommand Timeout(TimeSpan timeout)

Parameters

timeout TimeSpan

The time for the whole command, more than zero, or InfiniteTimeSpan for no bound.

Returns

CreateTableCommand

This command.

Remarks

The bound covers the look of IfNotExists(), the CreateTable with its retries, the wait of WaitUntilActive(), and turning time to live on. The call in flight is cancelled, where a token of the caller on a timer gives a bare OperationCanceledException.

Exceptions

ArgumentOutOfRangeException

timeout is zero, negative other than infinite, or above 4294967294 milliseconds, which this call finds out before a table is created.

ToRequest()

Builds the request that SendAsync(CancellationToken) would send, and sends nothing. It is the escape hatch for what this command does not model: change it with with, as in request with { Tags = tags }, and send it with client.CreateTableAsync(request). Stream(StreamViewType) is in it. WaitUntilActive(), IfNotExists() and TimeToLive(string) happen around the request and are not.

public CreateTableRequest ToRequest()

Returns

CreateTableRequest

The request.

ToRequestJson()

Renders the body of the CreateTable request that SendAsync(CancellationToken) would send, as DynamoDB JSON, and sends nothing: the JSON of ToRequest(). It is meant for tests and debugging. IfNotExists(), WaitUntilActive() and TimeToLive(string) make calls of their own around the request, which are not in it.

public string ToRequestJson()

Returns

string

The request body.

Remarks

Unlike ToRequestJson of the commands of items, it does not end the command, which stays this ordinary class: it can be rendered and then sent.

WaitUntilActive()

Makes SendAsync(CancellationToken) wait until the table and its global secondary indexes, those the entities of this command declare among them, are ACTIVE, for at most five minutes. See WaitUntilActive(TimeSpan).

public CreateTableCommand WaitUntilActive()

Returns

CreateTableCommand

This command.

WaitUntilActive(TimeSpan)

Makes SendAsync(CancellationToken) wait until the table and its global secondary indexes are ACTIVE, as WaitUntilActiveAsync(TimeSpan, CancellationToken) does. Without this, the table is CREATING when the call returns, and a read or write of it fails until it is active.

public CreateTableCommand WaitUntilActive(TimeSpan timeout)

Parameters

timeout TimeSpan

How long to wait after the table was created. InfiniteTimeSpan is not a bound and is rejected.

Returns

CreateTableCommand

This command.

Remarks

An index added later. With IfNotExists() over a table that exists, an index that UpdateTable added can still be CREATING, which takes minutes even on an empty table and often longer than the default of five, so give that wait half an hour or more.

Every global index that this command declares, by its entity and by Including<TOther>(), has to be listed and active, and a local one needs no wait. With IfNotExists() over an older table that lacks a declared index, nothing adds the index, so this fails instead of returning, with a TableNotActiveException that names it. It does not wait out the timeout for that: an index still not listed at the fifth look in a row, 1.5 seconds in, ends the wait, because a create lists all its indexes and only an UpdateTable sent before this command adds one. Add the index with UpdateTable in DynaSharp.Requests first. That needs an active table, which a table opened without an entity waits for, because it declares nothing: client.Table(name, "pk", "sk").WaitUntilActiveAsync(). Then this wait follows the backfill.

The timeout bounds the wait and nothing else, so the calls before it and time to live after it take their own time. Timeout(TimeSpan) bounds all of it and names the table when it runs out, where a CancellationToken on a timer gives a bare TaskCanceledException or OperationCanceledException, with no table and no index named.

Exceptions

ArgumentOutOfRangeException

timeout is negative, which this call finds out, so that no table is created for it.