Skip to content

DOC-8272-C3 --N1QL Checks #518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// = N1QL for Analytics vs. N1QL for Query
// :description: A comparison between N1QL for Analytics and N1QL for Query.
// :page-edition: Enterprise Edition
// BEGIN -- inclusion -- common-querybuilder.adoc
// Purpose -- describes the use of the query syntax
//
Expand All @@ -11,20 +14,122 @@
// END::REQUIRED EXTERNALS


== Introduction
// == Introduction

There are several minor but notable behavior differences between N1QL queries on Couchbase Lite and N1QL on Couchbase Server.
There are several minor but notable behavior differences between N1QL queries on Couchbase Lite and N1QL on Couchbase Server, ashown in <<tbl-diffs>>.

In some instance you can, if required, force Couchbase Lite to work in the same way as Couchbase Server. These instances are noted in the content below.

.N1QL Query Comparison (Couchbase Server _cf_ Couchbase Lite)
[#tbl-diffs, cols="^1,2a,2a, options="header"]
|===

=== OrderBy

In Couchbase Lite N1QL, result sequencing is based on the SQLite ordering described in https://sqlite.org/lang_select.html[SQLite select overview]
|
2+^h|N1QL Comparison

h| Feature
h| Couchbase Server
h| Couchbase Lite

| USE KEYS
| SELECT fname, email FROM tutorial USE KEYS ["dave", "ian"];
| SELECT fname, email FROM tutorial WHERE meta().id IN ["dave", "ian"];

| ON KEYS
| SELECT * FROM user u +
JOIN orders o ON KEYS ARRAY s.order_id FOR s IN u.order_history END;
| SELECT * FROM user u, u.order_history s +
JOIN orders o ON s.order_id = meta(o).id;

| ON KEY
| SELECT * FROM user u +
JOIN orders o ON KEY o.user_id FOR u;
| SELECT * FROM user u +
JOIN orders o ON meta(u).id = o.user_id;

| NEST
| SELECT * FROM user u +
NEST orders orders +
ON KEYS ARRAY s.order_id FOR s IN u.order_history END;
| SELECT u, orders FROM users u +
LET orders=(SELECT VALUE o FROM u.order_history s, orders o WHERE meta(o).id = s.order_id) +
WHERE EXISTS orders;

| LEFT OUTER NEST
| SELECT * FROM user u +
LEFT OUTER NEST orders orders +
ON KEYS ARRAY s.order_id FOR s IN u.order_history END;
| SELECT u, (SELECT VALUE o FROM u.order_history s, orders o WHERE meta(o).id = s.order_id) orders +
FROM users u;

| ARRAY
| ARRAY i FOR i IN [1, 2] END
| (SELECT VALUE i FROM [1, 2] AS i)

| ARRAY FIRST
| ARRAY FIRST arr
| arr[0]

| LIMIT l OFFSET o
| Allows OFFSET without LIMIT
| Doesn't support OFFSET without LIMIT

| UNION, INTERSECT, and EXCEPT
| All three are supported (with ALL and DISTINCT variants)
| Only UNION ALL is supported (and necessary for query expressability)

| OUTER JOIN
| Both LEFT and RIGHT OUTER JOIN supported
| Only LEFT OUTER JOIN supported (and necessary for query expressability)

| <, \<=, =, etc. operators
| Can compare either complex values or scalar values
| Only scalar values may be compared

| ORDER BY
|Result sequencing is based on specific rules described in {svr-n1ql-reference-orderby--xref}
|Result sequencing is based on the SQLite ordering described in https://sqlite.org/lang_select.html[SQLite select overview]

The ordering of _Dictionary_ and _Array_ objects is based on binary ordering.

For Couchbase Server, result sequencing is based on specific rules described in {svr-n1ql-reference-orderby--xref}

| SELECT DISTINCT
| Supported
| SELECT DISTINCT VALUE is supported when the returned values are scalars

| CREATE INDEX
| Supported
| Supported but different (e.g., typed)

| INSERT/UPSERT/DELETE
| Supported
| Unsupported (by design)
|===

// N1QL for Analytics generalizes N1QL for Query's syntax constructs such as `USE KEYS`, `ON KEYS`, `ON KEY`, `NEST`,
// `LEFT OUTER NEST` and `ARRAY` and thus eliminates cases where must-be-indexed or must-use-keys
// restrictions are required for certain N1QL for Query queries or expressions to be acceptable.
// In addition, the general composability of N1QL for Analytics queries eliminates the need for some of N1QL for Query's
// special syntax; for example, N1QL for Analytics does not require or support the IN/WITHIN subclauses of
// N1QL for Query's existential (SOME, ANY, or EVERY) expressions.

// Note that INSERT/UPSERT/DELETE are not supported at all in the Couchbase Analytics Service.
// Data is mutated in Couchbase Server, using the Couchbase Server SDK or N1QL for Query mutation, and
// the mutations will then be automatically synchronized into the Couchbase Analytics Service.
















== Boolean Logic Rules
Expand Down
78 changes: 61 additions & 17 deletions modules/ROOT/pages/_partials/commons/common-querybuilder-n1ql.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
:menuitem-delimiter: {nbsp}{nbsp}|{nbsp}{nbsp}
:count-result-text: The result set representing a count is a key-value pair. The key being the count name, as defined using `SelectResult.as`

include::_block-caveats.adoc[tags="code-disclaimer"]
include::{root-partials}_block-caveats.adoc[tags="code-disclaimer"]

// END::Local page attributes

Expand All @@ -26,8 +26,26 @@ NOTE: The examples used in this topic are based on the _Travel Sample_ app and d

Developers using Couchbase Lite for {param-title} can provide N1QL query strings using the N1QL Query API.
This API uses query statements of the form shown in <<ex-query-form>>.

The structure and semantics of the query format are based on that of Couchbase Server's N1QL query language -- see {svr-n1ql-reference--xref} and {svr-n1ql-datamodel--xref}.


== Running

Use the N1QLQuery class to define the N1QL query string on the database to be queried.
Then run the query using the`.execute()` method -- see {query-resultsets--xref} for how to work with result sets.

.Running a N1QL Query
[#ex-samplerun]
:param-tags: query-syntax-n1ql-params
include::partial$_block_tabbed_code_example.adoc[]
:param-tags!:


== Query Format

The API uses query statements of the form shown in <<ex-query-form>>.

[#ex-query-form]
.Query Format
====
Expand Down Expand Up @@ -58,19 +76,6 @@ The `SELECT`ed properties of documents matching this criteria will be returned i

TIP: We recommend working through the {url-n1ql-tutorial} as a good way to build your N1QL skills.

== Running

Use the N1QLQuery class to define the N1QL query string on the database to be queried.
Then run the query using the`.execute()` method -- see

The result set produced by N1QL and {querybuilder--xref} queries is the same -- see {query-resultsets--xref}.


.Running a N1QL Query
[#ex-samplerun]
:param-tags: query-syntax-n1ql-params
include::partial$_block_tabbed_code_example.adoc[]
:param-tags!:


[#lbl-select]
Expand Down Expand Up @@ -100,14 +105,50 @@ columnAlias = IDENTIFIER
<.> The select clause begins with the SELECT keyword.
+
--
* The optional `ALL` argument is used to specify tha the query should return ALL results (the default)
* The optional `ALL` argument is used to specify that the query should return ALL results (the default)
* The optional `DISTINCT` argument specifies that the query should remove duplicated results.
--

<.> selectResults is a list of columns projected in the query result. Each column is an expression which could be a property expression or any expressions or functions.
<.> selectResults is a list of columns projected in the query result.
Each column is an expression which could be a property expression or any expressions or functions.
You can use the wildcard *** to select all columns -- see <<Select Wildcard>>

<.> Use the optional `AS` argument to provides an alias name for a property. Each property can be aliased by putting the `AS <alias name>` after the column name.

==== Select Wildcard
When using the `SELECT *` option the column name (key) of the N1QL string is one of:

* The alias name if one was specified
* The data source name (or its alias if provided) as specified in the `FROM` clause.

This behavior is inline with that of Couchbase Server N1QL -- see example in <<tbl-selstar>>.


.Example Column Names for SELECT ***
[#tbl-selstar,cols="3m,2m"]
|===
| Query| Column Name

| SELECT * AS data FROM _
| data

| SELECT * FROM _
| _

| SELECT * FROM _default
| _default

|SELECT * FROM db
|db

|SELECT * FROM db AS store
|store

|===




=== Example

.SELECT properties
Expand Down Expand Up @@ -135,7 +176,7 @@ SELECT DISTINCT address.city <.>

====

See: <<lbl-resultsets>> for more on processing query results.
See: {query-resultsets--xref} for more on processing query results.

[#lbl-from]
== FROM
Expand Down Expand Up @@ -1907,6 +1948,8 @@ a|Returns one of the following strings, based on the value of expression:
|===


ifndef::is-c[]
// Section not valid for C
== QueryBuilder Differences

Couchbase Lite N1QL Query supports all QueryBuilder features, except _Predictive Query_ and _Index_. See <<tbl-qbldr-diffs>> for the features supported by N1QL but not by QueryBuilder.
Expand Down Expand Up @@ -1965,6 +2008,7 @@ TONUMBER
TOOBJECT
TOSTRING
|===
endif::is-c[]


// END --- inclusion -- common-querybuilder-n1ql.adoc
2 changes: 2 additions & 0 deletions modules/ROOT/pages/_partials/nav-skeleton.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ ifndef::is-c[** {querybuilder--xref}]
* {conflict--xref}
* {url-api-references}[API{nbsp}References]
* {dep-upgrade--xref}
ifndef::is-c[]
* Troubleshooting
include::{root-partials}nav-skeleton-troubleshooting-{param-platform}.adoc[]
endif::is-c[]
* Product Notes
** {release-notes--xref}
** {compatibility--xref}
Expand Down
31 changes: 21 additions & 10 deletions modules/c/examples/code_snippets/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ static void getting_started() {
FLString retrievedLanguage = FLValue_AsString(FLDict_Get(retrievedProperties, FLSTR("language")));
printf("Document ID :: %.*s", (int)retrievedID.size, (const char *)retrievedID.buf);
printf("Learning %.*s", (int)retrievedLanguage.size, (const char *)retrievedLanguage.buf);

CBLDocument_Release(mutableDoc);
CBLDocument_Release(docAgain);
FLSliceResult_Release(id);

// tag::query-syntax-n1ql-params[]
// Create a query to fetch documents of type SDK
int errorPos;
CBLQuery* query = CBLDatabase_CreateQuery(database, kCBLN1QLLanguage, FLSTR("SELECT * FROM _ WHERE type = \"SDK\""), &errorPos, &err);
Expand All @@ -100,6 +101,7 @@ static void getting_started() {
// Failed to run query, do error handling as above
return;
}
// end::query-syntax-n1ql-params[]

// TODO: Result set count?
CBLResultSet_Release(result);
Expand All @@ -118,7 +120,7 @@ static void getting_started() {
replConfig.database = database;
replConfig.endpoint = targetEndpoint;
replConfig.authenticator = basicAuth;

CBLReplicator* replicator = CBLReplicator_Create(&replConfig, &err);
CBLAuth_Free(basicAuth);
CBLEndpoint_Free(targetEndpoint);
Expand Down Expand Up @@ -173,7 +175,7 @@ static void test_replicator_conflict_resolve() {
// end::replication-conflict-resolver[]
}

static bool custom_conflict_handler(void* context, CBLDocument* documentBeingSaved,
static bool custom_conflict_handler(void* context, CBLDocument* documentBeingSaved,
const CBLDocument* conflictingDocument) {
FLDict currentProps = CBLDocument_Properties(conflictingDocument);
FLDict updatedProps = CBLDocument_Properties(documentBeingSaved);
Expand Down Expand Up @@ -205,7 +207,7 @@ static void test_save_with_conflict_handler() {
FLDict properties = CBLDocument_MutableProperties(mutableDocument);

/*
static bool custom_conflict_handler(void* context, CBLDocument* documentBeingSaved,
static bool custom_conflict_handler(void* context, CBLDocument* documentBeingSaved,
const CBLDocument* conflictingDocument) {
FLDict currentProps = CBLDocument_Properties(conflictingDocument);
FLDict updatedProps = CBLDocument_Properties(documentBeingSaved);
Expand Down Expand Up @@ -327,9 +329,9 @@ static void load_prebuilt() {
CBL_DeleteDatabase(FLSTR("travel-sample.cblite2"), kFLSliceNull, NULL);

// tag::prebuilt-database[]
// Note: Getting the path to a database is platform-specific. For desktop (including RPi)
// this can be a simple filesystem path. For iOS you need to get the path from the
// main bundle. For Android you need to extract it from your assets to a temporary directory
// Note: Getting the path to a database is platform-specific. For desktop (including RPi)
// this can be a simple filesystem path. For iOS you need to get the path from the
// main bundle. For Android you need to extract it from your assets to a temporary directory
// and then pass that path.

// NOTE: No error handling, for brevity (see getting started)
Expand All @@ -354,7 +356,7 @@ static void query_deleted_document() {
// NOTE: No error handling, for brevity (see getting started)

CBLError err;
CBLQuery* query = CBLDatabase_CreateQuery(db, kCBLN1QLLanguage,
CBLQuery* query = CBLDatabase_CreateQuery(db, kCBLN1QLLanguage,
FLSTR("SELECT meta().id FROM _ WHERE meta().deleted"), NULL, &err);
// end::query-deleted-documents[]

Expand Down Expand Up @@ -447,11 +449,20 @@ static void database_change_listener() {
}
*/

CBLListenerToken* token = CBLDatabase_AddDocumentChangeListener(db, FLSTR("user.john"),
CBLListenerToken* token = CBLDatabase_AddDocumentChangeListener(db, FLSTR("user.john"),
document_listener, NULL);
// end::document-listener[]
}

int main(int argc, char** argv) {
return 0;
}
}


// tag::query-index[]
// placeholder
// tag::query-index[]

// tag::fts-index[]
// placeholder
// tag::fts-index[]