diff --git a/README.md b/README.md index bd0ce50..0fabc81 100644 --- a/README.md +++ b/README.md @@ -7,82 +7,34 @@ [![Release](https://img.shields.io/github/release/btnguyen2k/gocosmos.svg?style=flat-square)](RELEASE-NOTES.md) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#database-drivers) -Go driver for [Azure Cosmos DB SQL API](https://azure.microsoft.com/en-us/services/cosmos-db/) which can be used with the standard [database/sql](https://golang.org/pkg/database/sql/) package. A REST client for [Azure Cosmos DB SQL API](https://azure.microsoft.com/en-us/services/cosmos-db/) is also included. +Go driver for [Azure Cosmos DB SQL API](https://azure.microsoft.com/services/cosmos-db/) which can be used with the standard [database/sql](https://golang.org/pkg/database/sql/) package. A REST client is also included. -## Example usage: REST client +## database/sql driver -```go -import ( - "os" - "database/sql" - "github.com/btnguyen2k/gocosmos" -) - -func main() { - cosmosDbConnStr := "AccountEndpoint=https://localhost:8081/;AccountKey=" - client, err := gocosmos.NewRestClient(nil, cosmosDbConnStr) - if err != nil { - panic(err) - } - - dbSpec := gocosmos.DatabaseSpec{Id:"mydb", Ru: 400} - result := client.CreateDatabase(dbSpec) - if result.Error() != nil { - panic(result.Error) - } - - // database "mydb" has been created successfuly -} -``` - -**Connection string syntax for Cosmos DB** - -> `AccountEndpoint=;AccountKey=;TimeoutMs=;Version=;AutoId=;InsecureSkipVerify=` - -- `AccountEndpoint`: (required) endpoint to access Cosmos DB. For example, the endpoint for Azure Cosmos DB Emulator running on local is `https://localhost:8081/`. -- `AccountKey`: (required) account key to authenticate. -- `TimeoutMs`: (optional) operation timeout in milliseconds. Default value is `10 seconds` if not specified. -- `Version`: (optional) version of Cosmos DB to use. Default value is `2018-12-31` if not specified. See: https://docs.microsoft.com/en-us/rest/api/cosmos-db/#supported-rest-api-versions. -- `AutoId`: (optional, available since [v0.1.2](RELEASE-NOTES.md)) see [auto id](#auto-id) session. -- `InsecureSkipVerify`: (optional, available since [v0.1.4](RELEASE-NOTES.md)) if `true`, disable CA verification for https endpoint (useful to run against test/dev env with local/docker Cosmos DB emulator). - -### Known issues - -**`GROUP BY` combined with `ORDER BY` is not supported** - -Azure Cosmos DB does not support `GROUP BY` combined with `ORDER BY` yet. You will receive the following error message: - -> 'ORDER BY' is not supported in presence of GROUP BY. - -**Cross-partition queries** - -When documents are spanned across partitions, they must be fetched from multiple `PkRangeId` and then merged to build -the final result. Due to this behaviour, some cross-partition queries might not work as expected. +Summary of supported SQL statements: -- *Paging cross-partition `OFFSET...LIMIT` queries using `max-count-item`*:
- Since documents must be fetched from multiple `PkRangeId`, the result is nondeterministic. - Moreover, calls to `RestClient.QueryDocumentsCrossPartition(...)` and `RestClient.QueryDocuments(...)` without - pagination (i.e. set `MaxCountItem=0`) may yield different results. +|Statement|Syntax| +|---------|-----------| +|Create a new database |`CREATE DATABASE [IF NOT EXISTS] `| +|Change database's throughput |`ALTER DATABASE WITH RU/MAXRU=`| +|Delete an existing database |`DROP DATABASE [IF EXISTS] `| +|List all existing databases |`LIST DATABASES`| +|Create a new collection |`CREATE COLLECTION [IF NOT EXISTS] [.] `| +|Change collection's throughput |`ALTER COLLECTION [.] WITH RU/MAXRU=`| +|Delete an existing collection |`DROP COLLECTION [IF EXISTS] [.]`| +|List all existing collections in a database|`LIST COLLECTIONS [FROM ]`| +|Insert a new document into collection |`INSERT INTO [.] ...`| +|Insert or replace a document |`UPSERT INTO [.] ...`| +|Delete an existing document |`DELETE FROM [.] WHERE id=`| +|Update an existing document |`UPDATE [.] SET ... WHERE id=`| +|Query documents in a collection |`SELECT [CROSS PARTITION] ... FROM ... [WITH database=]`| -- *Paging cross-partition `ORDER BY` queries with `max-count-item`*:
- Due to the fact that documents must be fetched from multiple `PkRangeId`, rows returned from calls to - `RestClient.QueryDocuments(...)` might not be in the expected order.
- *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or - `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). +See [supported SQL statements](SQL.md) for details. -- *Paging `SELECT DISTINCT` queries with `max-count-item`*:
- Due to the fact that documents must be fetched from multiple `PkRangeId`, rows returned from calls to - `RestClient.QueryDocuments(...)` might be duplicated.
- *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or - `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). - -- *`GROUP BY` combined with `max-count-item`*:
- Due to the fact that documents must be fetched from multiple `PkRangeId`, the result returned from calls to - `RestClient.QueryDocuments(...)` might not be as espected.
- *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or - `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). +> Azure Cosmos DB SQL API currently supports only [SELECT statement](https://learn.microsoft.com/azure/cosmos-db/nosql/query/select). +> `gocosmos` implements other statements by translating the SQL statement to [REST API calls](https://learn.microsoft.com/rest/api/cosmos-db/). -## Example usage: database/sql driver +### Example usage: ```go import ( @@ -110,21 +62,31 @@ func main() { **Data Source Name (DSN) syntax for Cosmos DB** -> `AccountEndpoint=;AccountKey=;TimeoutMs=;Version=;DefaultDb=;AutoId=;InsecureSkipVerify=` +_Note: line-break is for readability only!_ + +```connection +AccountEndpoint= +;AccountKey= +[;TimeoutMs=] +[;Version=] +[;DefaultDb|Db=] +[;AutoId=] +[;InsecureSkipVerify=] +``` - `AccountEndpoint`: (required) endpoint to access Cosmos DB. For example, the endpoint for Azure Cosmos DB Emulator running on local is `https://localhost:8081/`. - `AccountKey`: (required) account key to authenticate. - `TimeoutMs`: (optional) operation timeout in milliseconds. Default value is `10 seconds` if not specified. -- `Version`: (optional) version of Cosmos DB to use. Default value is `2018-12-31` if not specified. See: https://docs.microsoft.com/en-us/rest/api/cosmos-db/#supported-rest-api-versions. +- `Version`: (optional) version of Cosmos DB to use. Default value is `2018-12-31` if not specified. See: https://learn.microsoft.com/rest/api/cosmos-db/#supported-rest-api-versions. - `DefaultDb`: (optional, available since [v0.1.1](RELEASE-NOTES.md)) specify the default database used in Cosmos DB operations. Alias `Db` can also be used instead of `DefaultDb`. - `AutoId`: (optional, available since [v0.1.2](RELEASE-NOTES.md)) see [auto id](#auto-id) session. - `InsecureSkipVerify`: (optional, available since [v0.1.4](RELEASE-NOTES.md)) if `true`, disable CA verification for https endpoint (useful to run against test/dev env with local/docker Cosmos DB emulator). ### Auto-id -Azure Cosmos DB requires each document has a [unique ID](https://docs.microsoft.com/en-us/rest/api/cosmos-db/documents) that identifies the document. +Azure Cosmos DB requires each document has a [unique ID](https://learn.microsoft.com/rest/api/cosmos-db/documents) that identifies the document. When creating new document, if value for the unique ID field is not supplied `gocosmos` is able to generate one automatically. This feature is enabled -by specifying setting `AutoId=true` in the connection string (for REST client) or Data Source Name (for `database/sql` driver). If not specified, default +by specifying setting `AutoId=true` in the Data Source Name (for `database/sql` driver) or the connection string (for REST client). If not specified, default value is `AutoId=true`. _This settings is available since [v0.1.2](RELEASE-NOTES.md)._ @@ -145,53 +107,94 @@ rows from Cosmos DB. Thus, some limitations: - `OFFSET...LIMIT` without `ORDER BY`: -## Features +## REST client The REST client supports: - Database: `Create`, `Get`, `Delete`, `List` and change throughput. - Collection: `Create`, `Replace`, `Get`, `Delete`, `List` and change throughput. - Document: `Create`, `Replace`, `Get`, `Delete`, `Query` and `List`. -The `database/sql` driver supports: -- Database: - - `CREATE DATABASE` - - `ALTER DATABASE` - - `DROP DATABASE` - - `LIST DATABASES` -- Table/Collection: - - `CREATE TABLE/COLLECTION` - - `ALTER TABLE/COLLECTION` - - `DROP TABLE/COLLECTION` - - `LIST TABLES/COLLECTIONS` -- Item/Document: - - `INSERT` - - `UPSERT` - - `SELECT` - - `UPDATE` - - `DELETE` +### Example usage: -Summary of supported SQL statements: +```go +import ( + "os" + "database/sql" + "github.com/btnguyen2k/gocosmos" +) -|Statement|Syntax| -|---------|-----------| -|Create a new database |`CREATE DATABASE [IF NOT EXISTS] `| -|Change database's throughput |`ALTER DATABASE WITH RU/MAXRU=`| -|Delete an existing database |`DROP DATABASE [IF EXISTS] `| -|List all existing databases |`LIST DATABASES`| -|Create a new collection |`CREATE COLLECTION [IF NOT EXISTS] [.] `| -|Change collection's throughput |`ALTER COLLECTION [.] WITH RU/MAXRU=`| -|Delete an existing collection |`DROP COLLECTION [IF EXISTS] [.]`| -|List all existing collections in a database|`LIST COLLECTIONS [FROM ]`| -|Insert a new document into collection |`INSERT INTO [.] ...`| -|Insert or replace a document |`UPSERT INTO [.] ...`| -|Delete an existing document |`DELETE FROM [.] WHERE id=`| -|Update an existing document |`UPDATE [.] SET ... WHERE id=`| -|Query documents in a collection |`SELECT [CROSS PARTITION] ... FROM ... [WITH database=]`| +func main() { + cosmosDbConnStr := "AccountEndpoint=https://localhost:8081/;AccountKey=" + client, err := gocosmos.NewRestClient(nil, cosmosDbConnStr) + if err != nil { + panic(err) + } -See [supported SQL statements](SQL.md) for details. + dbSpec := gocosmos.DatabaseSpec{Id:"mydb", Ru: 400} + result := client.CreateDatabase(dbSpec) + if result.Error() != nil { + panic(result.Error) + } + + // database "mydb" has been created successfuly +} +``` + +**Connection string syntax for Cosmos DB** + +_Note: line-break is for readability only!_ + +``` +AccountEndpoint= +;AccountKey= +[;TimeoutMs=] +[;Version=] +[;AutoId=] +[;InsecureSkipVerify=`] +``` + +- `AccountEndpoint`: (required) endpoint to access Cosmos DB. For example, the endpoint for Azure Cosmos DB Emulator running on local is `https://localhost:8081/`. +- `AccountKey`: (required) account key to authenticate. +- `TimeoutMs`: (optional) operation timeout in milliseconds. Default value is `10 seconds` if not specified. +- `Version`: (optional) version of Cosmos DB to use. Default value is `2018-12-31` if not specified. See: https://learn.microsoft.com/rest/api/cosmos-db/#supported-rest-api-versions. +- `AutoId`: (optional, available since [v0.1.2](RELEASE-NOTES.md)) see [auto id](#auto-id) session. +- `InsecureSkipVerify`: (optional, available since [v0.1.4](RELEASE-NOTES.md)) if `true`, disable CA verification for https endpoint (useful to run against test/dev env with local/docker Cosmos DB emulator). + +### Known issues + +**`GROUP BY` combined with `ORDER BY` is not supported** + +Azure Cosmos DB does not support `GROUP BY` combined with `ORDER BY` yet. You will receive the following error message: + +> 'ORDER BY' is not supported in presence of GROUP BY. + +**Cross-partition queries** + +When documents are spanned across partitions, they must be fetched from multiple `PkRangeId` and then merged to build +the final result. Due to this behaviour, some cross-partition queries might not work as expected. + +- *Paging cross-partition `OFFSET...LIMIT` queries using `max-count-item`*:
+ Since documents must be fetched from multiple `PkRangeId`, the result is nondeterministic. + Moreover, calls to `RestClient.QueryDocumentsCrossPartition(...)` and `RestClient.QueryDocuments(...)` without + pagination (i.e. set `MaxCountItem=0`) may yield different results. -> Azure Cosmos DB SQL API currently supports only [SELECT statement](https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-select). -> `gocosmos` implements other statements by translating the SQL statement to REST API call to [Azure Cosmos DB REST API](https://docs.microsoft.com/en-us/rest/api/cosmos-db/). +- *Paging cross-partition `ORDER BY` queries with `max-count-item`*:
+ Due to the fact that documents must be fetched from multiple `PkRangeId`, rows returned from calls to + `RestClient.QueryDocuments(...)` might not be in the expected order.
+ *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or + `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). + +- *Paging `SELECT DISTINCT` queries with `max-count-item`*:
+ Due to the fact that documents must be fetched from multiple `PkRangeId`, rows returned from calls to + `RestClient.QueryDocuments(...)` might be duplicated.
+ *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or + `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). + +- *`GROUP BY` combined with `max-count-item`*:
+ Due to the fact that documents must be fetched from multiple `PkRangeId`, the result returned from calls to + `RestClient.QueryDocuments(...)` might not be as espected.
+ *Workaround*: if you can afford the memory, use `RestClient.QueryDocumentsCrossPartition(...)` or + `RestClient.QueryDocuments(...)` without pagination (i.e. set `MaxCountItem=0`). ## License diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index deacbce..e9b4923 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,6 +1,6 @@ -# gocosmos release notes +# gocosmos - Release notes -## 2023-06-xx - v0.2.1 +## 2023-06-09 - v0.2.1 - Bug fixes, Refactoring & Enhancements. diff --git a/SQL.md b/SQL.md index 1cb7bde..27f14cf 100644 --- a/SQL.md +++ b/SQL.md @@ -1,5 +1,5 @@ -# gocosmos supported SQL statements - +# gocosmos - Supported SQL statements + - Database: [CREATE DATABASE](#create-database), [ALTER DATABASE](#alter-database), [DROP DATABASE](#drop-database), [LIST DATABASES](#list-databases). - Collection: [CREATE COLLECTION](#create-collection), [ALTER COLLECTION](#alter-collection), [DROP COLLECTION](#drop-collection), [LIST COLLECTIONS](#list-collections). - Document: [INSERT](#insert), [UPSERT](#upsert), [UPDATE](#update), [DELETE](#delete), [SELECT](#select). @@ -10,100 +10,123 @@ Suported statements: `CREATE DATABASE`, `ALTER DATABASE`, `DROP DATABASE`, `LIST #### CREATE DATABASE -Summary: create a new database. - -Syntax: `CREATE DATABASE [IF NOT EXISTS] [WITH RU|MAXRU=]`. +Description: create a new database. -- This statement returns error (StatusCode=409) if the specified database already existed. If `IF NOT EXISTS` is specified, the error is silently ignored. -- Provisioned capacity can be optionally specified via `WITH RU=` or `WITH MAXRU=`. +Syntax: -> If none of RU or MAXRU is provided, the new database is created without provisioned capacity, and this _cant_ be changed latter. +```sql +CREATE DATABASE [IF NOT EXISTS] [WITH RU|MAXRU=] +``` Example: ```go -_, err := db.Exec("CREATE DATABASE IF NOT EXISTS mydb WITH ru=400") +dbresult, err := db.Exec("CREATE DATABASE IF NOT EXISTS mydb WITH ru=400") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrConflict` if the specified database already existed. If `IF NOT EXISTS` is specified, `RowsAffected()` returns `(0, nil)`. +- Provisioned capacity can be optionally specified via `WITH RU=` or `WITH MAXRU=`. + - If none of `RU` or `MAXRU` is provided, the new database is created without provisioned capacity, and this _cannot_ be changed latter. + - Only one of `RU` and `MAXRU` options should be specified, _not both_; error is returned if both optiosn are specified. + [Back to top](#top) #### ALTER DATABASE -Summary: change database's throughput (since [v0.1.1](RELEASE-NOTES.md)). +Description: change database's throughput (since [v0.1.1](RELEASE-NOTES.md)). -Syntax: `ALTER DATABASE WITH RU|MAXRU=`. +Syntax: -- Only one of RU or MAXRU must be specified. +```sql +ALTER DATABASE WITH RU|MAXRU= +``` Example: ```go -_, err := db.Exec("ALTER DATABASE mydb WITH ru=400") +dbresult, err := db.Exec("ALTER DATABASE mydb WITH ru=400") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrNotFound` if the specified database does not exist. +- Only one of `RU` and `MAXRU` options should be specified, _not both_; error is returned if both optiosn are specified. + [Back to top](#top) #### DROP DATABASE -Summary: delete an existing database. +Description: delete an existing database. -Syntax: `DROP DATABASE [IF EXISTS] `. +Syntax: -- This statement returns error (StatusCode=404) if the specified database does not exist. If `IF EXISTS` is specified, the error is silently ignored. +```sql +DROP DATABASE [IF EXISTS] +``` Example: ```go -_, err := db.Exec("DROP DATABASE IF EXISTS mydb") +dbresult, err := db.Exec("DROP DATABASE IF EXISTS mydb") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrNotFound` if the specified database does not exist. If `IF EXISTS` is specified, `RowsAffected()` returns `(0, nil)`. + [Back to top](#top) #### LIST DATABASES -Summary: list all existing databases. +Description: list all existing databases. + +Syntax: -Syntax: `LIST DATABASES`. +```sql +LIST DATABASES +``` Example: ```go dbRows, err := db.Query("LIST DATABASES") if err != nil { - panic(err) + panic(err) } colTypes, err := dbRows.ColumnTypes() if err != nil { - panic(err) + panic(err) } numCols := len(colTypes) for dbRows.Next() { - vals := make([]interface{}, numCols) - scanVals := make([]interface{}, numCols) - for i := 0; i < numCols; i++ { - scanVals[i] = &vals[i] - } - if err := dbRows.Scan(scanVals...); err == nil { - row := make(map[string]interface{}) - for i, v := range colTypes { - row[v.Name()] = vals[i] - } - fmt.Println("Database:", row) - } else if err != sql.ErrNoRows { - panic(err) - } + vals := make([]interface{}, numCols) + scanVals := make([]interface{}, numCols) + for i := 0; i < numCols; i++ { + scanVals[i] = &vals[i] + } + if err := dbRows.Scan(scanVals...); err == nil { + row := make(map[string]interface{}) + for i, v := range colTypes { + row[v.Name()] = vals[i] + } + fmt.Println("Database:", row) + } else if err != sql.ErrNoRows { + panic(err) + } } ``` @@ -117,80 +140,115 @@ Suported statements: `CREATE COLLECTION`, `ALTER COLLECTION`, `DROP COLLECTION`, #### CREATE COLLECTION -Summary: create a new collection. +Description: create a new collection. Alias: `CREATE TABLE`. -Syntax: `CREATE COLLECTION [IF NOT EXISTS] [.] [WITH RU|MAXRU=ru] [WITH UK=/path1:/path2,/path3;/path4]`. +Syntax: -- This statement returns error (StatusCode=409) if the specified collection already existed. If `IF NOT EXISTS` is specified, the error is silently ignored. -- Partition key must be specified using `WITH pk=`. If partition key is larger than 100 bytes, use `largepk` instead. -- Provisioned capacity can be optionally specified via `WITH RU=` or `WITH MAXRU=`. -- Unique keys are optionally specified via `WITH uk=/uk1_path:/uk2_path1,/uk2_path2:/uk3_path`. Each unique key is a comma-separated list of paths (e.g. `/uk_path1,/uk_path2`); unique keys are separated by colons (e.g. `/uk1:/uk2:/uk3`). +```sql +CREATE COLLECTION [IF NOT EXISTS] [.] + +[[,] WITH RU|MAXRU=ru] +[[,] WITH UK=/path1:/path2,/path3;/path4] +``` + +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go -_, err := db.Exec("CREATE COLLECTION IF NOT EXISTS mydb.mytable WITH pk=/username WITH ru=400 WITH uk=/email") +dbresult, err := db.Exec("CREATE COLLECTION IF NOT EXISTS mydb.mytable WITH pk=/username WITH ru=400 WITH uk=/email") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrConflict` if the specified collection already existed. If `IF NOT EXISTS` is specified, `RowsAffected()` returns `(0, nil)`. +- Partition key must be specified using `WITH pk=`. If partition key is larger than 100 bytes, use `WITH pk=` instead. +- Provisioned capacity can be optionally specified via `WITH RU=` or `WITH MAXRU=`. + - Only one of `RU` and `MAXRU` options should be specified, _not both_; error is returned if both optiosn are specified. +- Unique keys are optionally specified via `WITH uk=/uk1_path:/uk2_path1,/uk2_path2:/uk3_path`. Each unique key is a comma-separated list of paths (e.g. `/uk_path1,/uk_path2`); unique keys are separated by colons (e.g. `/uk1:/uk2:/uk3`). + [Back to top](#top) #### ALTER COLLECTION -Summary: change collection's throughput (since [v0.1.1](RELEASE-NOTES.md)). +Description: change collection's throughput (since [v0.1.1](RELEASE-NOTES.md)). Alias: `ALTER TABLE`. -Syntax: `ALTER COLLECTION [.] WITH RU|MAXRU=`. +Syntax: + +```sql +ALTER COLLECTION [.] WITH RU|MAXRU= +``` -- Only one of RU or MAXRU must be specified. +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go -_, err := db.Exec("ALTER COLLECTION mydb.mytable WITH ru=400") +dbresult, err := db.Exec("ALTER COLLECTION mydb.mytable WITH ru=400") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrNotFound` if the specified database does not exist. +- Only one of `RU` and `MAXRU` options should be specified, _not both_; error is returned if both optiosn are specified. + [Back to top](#top) #### DROP COLLECTION -Summary: delete an existing collection. +Description: delete an existing collection. Alias: `DROP TABLE`. -Syntax: `DROP COLLECTION [IF EXISTS] [.]`. +Syntax: + +```sql +DROP COLLECTION [IF EXISTS] [.] +``` -- This statement returns error (StatusCode=404) if the specified collection does not exist. If `IF EXISTS` is specified, the error is silently ignored. +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go -_, err := db.Exec("DROP COLLECTION IF EXISTS mydb.mytable") +dbresult, err := db.Exec("DROP COLLECTION IF EXISTS mydb.mytable") if err != nil { - panic(err) + panic(err) } +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. +- This statement returns error `ErrNotFound` if the specified database does not exist. If `IF EXISTS` is specified, `RowsAffected()` returns `(0, nil)`. + [Back to top](#top) #### LIST COLLECTIONS -Summary: list all existing collections in a database. +Description: list all existing collections in a database. Alias: `LIST TABLES`. -Syntax: `LIST COLLECTIONS [FROM ]`. +Syntax: + +```sql +LIST COLLECTIONS [FROM ] +``` + +> `FROM ` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go @@ -201,24 +259,24 @@ if err != nil { colTypes, err := dbRows.ColumnTypes() if err != nil { - panic(err) + panic(err) } numCols := len(colTypes) for dbRows.Next() { - vals := make([]interface{}, numCols) - scanVals := make([]interface{}, numCols) - for i := 0; i < numCols; i++ { - scanVals[i] = &vals[i] - } - if err := dbRows.Scan(scanVals...); err == nil { - row := make(map[string]interface{}) - for i, v := range colTypes { - row[v.Name()] = vals[i] - } - fmt.Println("Collection:", row) - } else if err != sql.ErrNoRows { - panic(err) - } + vals := make([]interface{}, numCols) + scanVals := make([]interface{}, numCols) + for i := 0; i < numCols; i++ { + scanVals[i] = &vals[i] + } + if err := dbRows.Scan(scanVals...); err == nil { + row := make(map[string]interface{}) + for i, v := range colTypes { + row[v.Name()] = vals[i] + } + fmt.Println("Collection:", row) + } else if err != sql.ErrNoRows { + panic(err) + } } ``` @@ -232,170 +290,166 @@ Suported statements: `INSERT`, `UPSERT`, `UPDATE`, `DELETE`, `SELECT`. #### INSERT -Summary: insert a new document into an existing collection. +Description: insert a new document into an existing collection. -Syntax: `INSERT INTO [.] (, ,...) VALUES (, ,...)`. +Syntax: -A value is either: -- a placeholder -- a `null` -- a number -- a boolean (`true/false`) -- a string (wrapped by double quotes) that must be a valid JSON: - - a string value in JSON (include the double quotes), for example `"\"a string\""` - - a number value in JSON (include the double quotes), for example `"123"` - - a boolean value in JSON (include the double quotes), for example `"true"` - - a null value in JSON (include the double quotes), for example `"null"` - - a map value in JSON (include the double quotes), for example `"{\"key\":\"value\"}"` - - a list value in JSON (include the double quotes), for example `"[1,true,null,\"string\"]"` +```sql +INSERT INTO [.] (, ,...) VALUES (, ,...) +``` -> Placeholder is a number prefixed by `$` or `@` or `:`, for example `$1`, `@2` or `:3`. The first placeholder is 1, the second one is 2 and so on. +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go -sql := `INSERT INTO mydb.mytable (a, b, c, d, e) VALUES (1, "\"a string\"", true, "[1,true,null,\"string\"]", $1)` -result, err := db.Exec(sql, map[string]interface{}{"key":"value"}, "mypk") -if err != nil { - panic(err) -} - -numRows, err := result.RowsAffected() +sql := `INSERT INTO mydb.mytable (a, b, c, d, e) VALUES (1, "\"a string\"", :1, @2, $3)` +dbresult, err := db.Exec(sql, true, []interface{}{1, true, nil, "string"}, map[string]interface{}{"key":"value"}, "mypk") if err != nil { - panic(err) + panic(err) } -fmt.Println("Number of rows affected:", numRows) +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. > Value of partition key _must_ be supplied at the last argument of `db.Exec()` call. + +A value is either: +- a placeholder - which is a number prefixed by `$` or `@` or `:`, for example `$1`, `@2` or `:3`. Placeholders are 1-based index, that means starting from 1. +- a `null` +- a number, for example `12.3`. +- a boolean (`true/false`) +- a JSON string (wrapped by double quotes), must be a valid JSON: + - a string value in JSON (include the double quotes), for example `"\"a string\""` + - a number value in JSON (include the double quotes), for example `"123"` + - a boolean value in JSON (include the double quotes), for example `"true"` + - a null value in JSON (include the double quotes), for example `"null"` + - a map value in JSON (include the double quotes), for example `"{\"key\":\"value\"}"` + - a list value in JSON (include the double quotes), for example `"[1,true,null,\"string\"]"` + [Back to top](#top) #### UPSERT -Summary: insert a new document or replace an existing one. +Description: insert a new document or replace an existing one. + +Syntax & Usage: similar to [INSERT](#insert). -Syntax & Usage: see [INSERT](#insert). +```sql +UPSERT INTO [.] (, ,...) VALUES (, ,...) +``` [Back to top](#top) #### DELETE -Summary: delete an existing document. +Description: delete an existing document. -Syntax: `DELETE FROM [.] WHERE id=` +Syntax: -- `DELETE` removes only one document specified by id. -- `` is treated as string, i.e. `WHERE id=abc` has the same effect as `WHERE id="abc"`. A placeholder can be use as ``. +```sql +DELETE FROM [.] WHERE id= +``` -> Placeholder is a number prefixed by `$` or `@` or `:`, for example `$1`, `@2` or `:3`. The first placeholder is 1, the second one is 2 and so on. +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go sql := `DELETE FROM mydb.mytable WHERE id=@1` -result, err := db.Exec(sql, "myid", "mypk") -if err != nil { - panic(err) -} - -numRows, err := result.RowsAffected() +dbresult, err := db.Exec(sql, "myid", "mypk") if err != nil { - panic(err) + panic(err) } -fmt.Println("Number of rows affected:", numRows) +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. > Value of partition key _must_ be supplied at the last argument of `db.Exec()` call. +- `DELETE` removes only one document specified by id. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. If no document matched, `RowsAffected()` returns `(0, nil)`. +- `` is treated as string, i.e. `WHERE id=abc` has the same effect as `WHERE id="abc"`. A placeholder can be used in the place of ``. See [here](#value) for more details on values and placeholders. + [Back to top](#top) #### UPDATE -Summary: update an existing document. +Description: update an existing document. -Syntax: `UPDATE [.] SET =,=,...=, WHERE id=` +Syntax: -- `UPDATE` modifies only one document specified by id. -- `` is treated as string, i.e. `WHERE id=abc` has the same effect as `WHERE id="abc"`. A placeholder can be use as ``. -- A value is either: - - a placeholder - - a `null` - - a number - - a boolean (`true/false`) - - a string (wrapped by double quotes) that must be a valid JSON: - - a string value in JSON (include the double quotes), for example `"\"a string\""` - - a number value in JSON (include the double quotes), for example `"123"` - - a boolean value in JSON (include the double quotes), for example `"true"` - - a null value in JSON (include the double quotes), for example `"null"` - - a map value in JSON (include the double quotes), for example `"{\"key\":\"value\"}"` - - a list value in JSON (include the double quotes), for example `"[1,true,null,\"string\"]"` - -> Placeholder is a number prefixed by `$` or `@` or `:`, for example `$1`, `@2` or `:3`. The first placeholder is 1, the second one is 2 and so on. +```sql +UPDATE [.] SET =[,=,...=] WHERE id= +``` + +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: ```go sql := `UPDATE mydb.mytable SET a=1, b="\"a string\"", c=true, d="[1,true,null,\"string\"]", e=:2 WHERE id=@1` -result, err := db.Exec(sql, "myid", map[string]interface{}{"key":"value"}, "mypk") -if err != nil { - panic(err) -} - -numRows, err := result.RowsAffected() +dbresult, err := db.Exec(sql, "myid", map[string]interface{}{"key":"value"}, "mypk") if err != nil { - panic(err) + panic(err) } -fmt.Println("Number of rows affected:", numRows) +fmt.Println(dbresult.RowsAffected()) ``` > Use `sql.DB.Exec` to execute the statement, `Query` will return error. > Value of partition key _must_ be supplied at the last argument of `db.Exec()` call. +- `UPDATE` modifies only one document specified by id. +- Upon successful execution, `RowsAffected()` returns `(1, nil)`. If no document matched, `RowsAffected()` returns `(0, nil)`. +- `` is treated as string, i.e. `WHERE id=abc` has the same effect as `WHERE id="abc"`. A placeholder can be used in the place of ``. +- See [here](#value) for more details on values and placeholders. + [Back to top](#top) #### SELECT -Summary: query documents in a collection. +Description: query documents in a collection. -Syntax: `SELECT [CROSS PARTITION] ... FROM ... [WITH database=] [WITH collection=] [WITH cross_partition=true]` +Syntax: -The `SELECT` query follows [Azure Cosmos DB's SQL grammar](https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-select) with a few extensions: -- If the collection is partitioned, specify `CROSS PARTITION` to allow execution across multiple partitions. This clause is not required if query is to be executed on a single partition. Cross-partition execution can also be enabled using `WITH cross_partition=true`. -- The database on which the query is execute _must_ be specified via `WITH database=` or `WITH db=` or with default database option via DSN. -- The collection to query from can be optionally specified via `WITH collection=` or `WITH table=`. If not specified, the collection name is extracted from the `FROM ` clause. -- Placeholder syntax: `@i`, `$i` or `:i` (where i denotes the i-th parameter, the first parameter is 1). +```sql +SELECT [CROSS PARTITION] ... FROM ... +[WITH database=] +[[,] WITH collection=] +[[,] WITH cross_partition=true] +``` + +> `` can be ommitted if `DefaultDb` is supplied in the Data Source Name (DSN). Example: single partition, collection name is extracted from the `FROM...` clause ```go sql := `SELECT * FROM mytable c WHERE c.age>@1 AND c.class=$2 AND c.pk="\"mypk\"" WITH db=mydb` dbRows, err := db.Query(sql, 21, "Grade A") if err != nil { - panic(err) + panic(err) } colTypes, err := dbRows.ColumnTypes() if err != nil { - panic(err) + panic(err) } numCols := len(colTypes) for dbRows.Next() { - vals := make([]interface{}, numCols) - scanVals := make([]interface{}, numCols) - for i := 0; i < numCols; i++ { - scanVals[i] = &vals[i] - } - if err := dbRows.Scan(scanVals...); err == nil { - row := make(map[string]interface{}) - for i, v := range colTypes { - row[v.Name()] = vals[i] - } - fmt.Println("Collection:", row) - } else if err != sql.ErrNoRows { - panic(err) - } + vals := make([]interface{}, numCols) + scanVals := make([]interface{}, numCols) + for i := 0; i < numCols; i++ { + scanVals[i] = &vals[i] + } + if err := dbRows.Scan(scanVals...); err == nil { + row := make(map[string]interface{}) + for i, v := range colTypes { + row[v.Name()] = vals[i] + } + fmt.Println("Row:", row) + } else if err != sql.ErrNoRows { + panic(err) + } } ``` @@ -404,10 +458,16 @@ Example: cross partition, collection name is explicitly specified via `WITH...` sql := `SELECT CROSS PARTITION * FROM c WHERE c.age>@1 AND c.active=true WITH db=mydb WITH table=mytable` dbRows, err := db.Query(sql, 21) if err != nil { - panic(err) + panic(err) } ``` > Use `sql.DB.Query` to execute the statement, `Exec` will return error. +The `SELECT` query follows [Azure Cosmos DB's SQL grammar](https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-select) with a few extensions: +- If the collection is partitioned, specify `CROSS PARTITION` to allow execution across multiple partitions. This clause is not required if query is to be executed on a single partition. Cross-partition execution can also be enabled using `WITH cross_partition=true`. +- The database on which the query is execute _must_ be specified via `WITH database=` or `WITH db=` or with default database option via DSN. +- The collection to query from can be optionally specified via `WITH collection=` or `WITH table=`. If not specified, the collection name is extracted from the `FROM ` clause. +- See [here](#value) for more details on values and placeholders. + [Back to top](#top)