// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
= Table API

To execute table operations on a specific table, you need to get a specific view of the table and use one of its methods. You can only create new tables by using SQL API.

Ignite supports mapping user objects to table tuples. This ensures that objects created in any programming language can be used for key-value operations directly.

== Getting a Table Instance

First, get an instance of the table. To obtain an instance of table, use the `IgniteTables.table(String)` method. You can also use `IgniteTables.tables()` method to list all existing tables.


[tabs]
--
tab:Java[]
[source, java]
----
IgniteTables tableApi = client.tables();
List<Table> existingTables = tableApi.tables();
Table firstTable = existingTables.get(0);

Table myTable = tableApi.table("MY_TABLE");
----

tab:.NET[]
[source, csharp]
----
var existingTables = await Client.Tables.GetTablesAsync();
var firstTable = existingTables[0];

var myTable = await Client.Tables.GetTableAsync("MY_TABLE");
----

tab:C++[]
[source, cpp]
----
using namespace ignite;

auto table_api = client.get_tables();
std::vector<table> existing_tables = table_api.get_tables();
table first_table = existing_tables.front();

std::optional<table> my_table = table_api.get_table("MY_TABLE);
----
--

== Basic Table Operations

Once you've got a table you need to get a specific view to choose how you want to operate table records.

=== Binary Record View

A binary record view. It can be used to operate table tuples directly.

[tabs]
--
tab:Java[]
[source, java]
----
RecordView<Tuple> view = table.recordView();

Tuple fullRecord = Tuple.create()
  .set("id", 42)
  .set("name", "John Doe");

view.upsert(null, fullRecord);

Tuple keyRecord = Tuple.create().set("id", 42);

Tuple resRecord = view.get(null, keyRecord);

assert resRecord.columnCount() == 2;
assert resRecord.intValue("id") == 42;
assert resRecord.stringValue("name").equals("John Doe");
----

tab:.NET[]
[source, csharp]
----
IRecordView<IIgniteTuple> view = table.RecordBinaryView;

IIgniteTuple fullRecord = new IgniteTuple
{
  ["id"] = 42,
  ["name"] = "John Doe"
};

await view.UpsertAsync(transaction: null, fullRecord);

IIgniteTuple keyRecord = new IgniteTuple { ["id"] = 42 };
(IIgniteTuple value, bool hasValue) = await view.GetAsync(transaction: null, keyRecord);

Debug.Assert(hasValue);
Debug.Assert(value.FieldCount == 2);
Debug.Assert(value["id"] as int? == 42);
Debug.Assert(value["name"] as string == "John Doe");
----

tab:C++[]
[source, cpp]
----
record_view<ignite_tuple> view = table.get_record_binary_view();

ignite_tuple record{
  {"id", 42},
  {"name", "John Doe"}
};

view.upsert(nullptr, record);
std::optional<ignite_tuple> res_record = view.get(nullptr, {"id", 42});

assert(res_record.has_value());
assert(res_record->column_count() == 2);
assert(res_record->get<std::int64_t>("id") == 42);
assert(res_record->get<std::string>("name") == "John Doe");
----

--

=== Record View

A record view mapped to a user type. It can be used to operate table using user objects which are mapped to table tuples.

[tabs]
--
tab:Java[]
[source, java]
----
RecordView<Pojo> pojoView = table.recordView(Mapper.of(Pojo.class));

pojoView.upsert(null, new Pojo(42, "John Doe"));
Pojo resRecord = pojoView.get(null, new Pojo(42));

assert resRecord.id == 42;
assert resRecord.name.equals("John Doe");
----

tab:.NET[]
[source, csharp]
----
var pocoView = table.GetRecordView<Poco>();

await pocoView.UpsertAsync(transaction: null, new Poco(42, "John Doe"));
var (value, hasValue) = await pocoView.GetAsync(transaction: null, new Poco(42));

Debug.Assert(hasValue);
Debug.Assert(value.Name == "John Doe");

public record Poco(long Id, string? Name = null);
----

tab:C++[]
[source, cpp]
----
record_view<person> view = table.get_record_view<person>();

person record(42, "John Doe");

view.upsert(nullptr, record);
std::optional<person> res_record = view.get(nullptr, person{42});

assert(res.has_value());
assert(res->id == 42);
assert(res->name == "John Doe");
----

--

=== Key-Value Binary View

A binary key-value view. It can be used to operate table using key and value tuples separately.

[tabs]
--
tab:Java[]
[source, java]
----
KeyValueView<Tuple, Tuple> kvView = table.keyValueView();

Tuple key = Tuple.create().set("id", 42)
Tuple val = Tuple.create().set("name", "John Doe");

kvView.put(null, key, val);
Tuple res = kvView.get(null, key);

assert res.columnCount() == 1;
assert res.stringValue("name").equals("John Doe");
----

tab:.NET[]
[source, csharp]
----
IKeyValueView<IIgniteTuple, IIgniteTuple> kvView = table.KeyValueBinaryView;

IIgniteTuple key = new IgniteTuple { ["id"] = 42 };
IIgniteTuple val = new IgniteTuple { ["name"] = "John Doe" };

await kvView.PutAsync(transaction: null, key, val);
(IIgniteTuple? value, bool hasValue) = await kvView.GetAsync(transaction: null, key);

Debug.Assert(hasValue);
Debug.Assert(value.FieldCount == 1);
Debug.Assert(value["name"] as string == "John Doe");
----

tab:C++[]
[source, cpp]
----
key_value_view<ignite_tuple, ignite_tuple> kv_view = table.get_key_value_binary_view();

ignite_tuple key_tuple{{"id", 42}};
ignite_tuple val_tuple{{"name", "John Doe"}};

kv_view.put(nullptr, key_tuple, val_tuple);
std::optional<ignite_tuple> res_tuple = kv_view.get(nullptr, key_tuple);

assert(res_tuple.has_value());
assert(res_tuple->column_count() == 2);
assert(res_tuple->get<std::int64_t>("id") == 42);
assert(res_tuple->get<std::string>("name") == "John Doe");
----

--


=== Key-Value View

A key-value view with user objects. It can be used to operate table using key and value user objects mapped to table tuples.

[tabs]
--
tab:Java[]
[source, java]
----
KeyValueView<Long, Pojo> pojoView =
  table.keyValueView(Mapper.of(Long.class), Mapper.of(Pojo.class));

pojoView.put(null, 42, new Pojo("John Doe"));
Pojo val = pojoView.get(null, 42);

assert val.name.equals("John Doe");
----

tab:.NET[]
[source, csharp]
----
IKeyValueView<long, Poco> kvView = table.GetKeyValueView<long, Poco>();

await kvView.PutAsync(transaction: null, 42, new Poco(Id: 0, Name: "John Doe"));
(Poco? value, bool hasValue) = await kvView.GetAsync(transaction: null, 42);

Debug.Assert(hasValue);
Debug.Assert(value.Name == "John Doe");

public record Poco(long Id, string? Name = null);
----

tab:C++[]
[source, cpp]
----
key_value_view<person, person> kv_view = table.get_key_value_view<person, person>();

kv_view.put(nullptr, {42}, {"John Doe"});
std::optional<person> res = kv_view.get(nullptr, {42});

assert(res.has_value());
assert(res->id == 42);
assert(res->name == "John Doe");
----
--


== Criterion Queries

Ignite 3 provides the criterion queries that can be used to retrieve data from tables. Criterion queries work with any type of view, returning the appropriate data to the query specified.

The example below shows how you can execute a query within an implicit transaction:

[tabs]
--
tab:Java[]
[source, java]
----
try (Cursor<Entry<Tuple, Tuple>> cursor = kvView().query(
        null,
        and(columnValue("City", equalTo("New York")), columnValue("Salary", greaterThan(10000)))
)) {
    // ...
}


----
--

The comparison query are specified by using the `query()` method, and providing the comparison criteria in the `columnValue` method.

You can also specify the specific transaction to execute the query in to perform the query in that specific transaction.

[tabs]
--
tab:Java[]
[source, java]
----
var tx = client.transactions().begin();

try (Cursor<Entry<Tuple, Tuple>> cursor = kvView().query(
        tx,
        and(columnValue("City", equalTo("New York")), columnValue("Salary", greaterThan(10000)))
)) {
    // ...
}

tx.close();
----
--

=== Asynchronous Queries

You can also perform the query asynchronously by using the `queryAsync` method. This way the query is executed without blocking the thread. For example, you can execute the above query asynchronously:

[tabs]
--
tab:Java[]
----
view.queryAsync(null, and(columnValue("City", equalTo("New York")), columnValue("Salary", greaterThan(10000)))
    .thenCompose(this::fetchAllRowsInto)
    .join();
----
--

This operation uses the `thenCompose()` method to handle the query results asynchronously in the user-defined `fetchAllRowsInto()` method. Here is how this method may look like:

[tabs]
--
tab:Java[]
----
private static CompletionStage<Void> fetchAllRowsInto(AsyncCursor<Entry<Tuple, Tuple>> cursor) {
    // Process the current page.
    for (var row : cursor.currentPage()) {
       // ...
    }
    // Finish processing if no more data is currently available.
    if (!cursor.hasMorePages()) {
        return nullCompletedFuture();
    }
    // Request for the next page, then subscribe to the response.
    return cursor.fetchNextPage().thenCompose(this::fetchAllRowsInto);
}
----
--


=== Comparison Expressions

The following expressions are supported in criterion queries:

[cols="15%,60%,25%",opts="header"]
|======
|Expression|Description|Example
|`equalTo`|Checks if the object is equal to the value.|`columnValue("City", equalTo("New York"))`
|`notEqualTo`|Checks if the object is not equal to the value.|`columnValue("City", notEqualTo("New York"))`
|`greaterThan`|Checks if the object is greater than the value.|`columnValue("Salary", greaterThan(10000))`
|`greaterThanOrEqualTo`|Checks if the object is greater than or equal to the value.|`columnValue("Salary", greaterThanOrEqualTo(10000))`
|`lessThan`|Checks if the object is less than the value.|`columnValue("Salary", lessThan(10000))`
|`lessThanOrEqualTo`|Checks if the object is less than or equal to the value.|`columnValue("Salary", lessThanOrEqualTo(10000))`
|`nullValue`|Checks if the object is null.|`columnValue("City", nullValue()`
|`notNullValue`|Checks if the object is not null.|`columnValue("City", notNullValue())`
|`in`|Checks if the object is in the collection.|`columnValue("City", in("New York", "Washington"))`
|`notIn`|Checks if the object is not in the collection.|`columnValue("City", notIn("New York", "Washington"))`
|======

=== Comparison Operators

The following operators are supported in criterion queries:


[cols="15%,60%,25%",opts="header"]
|======
|Operator|Description|Example
|`not`|Negates the condition.|`not(columnValue("City", equalTo("New York")))`
|`and`|Used to evaluate multiple conditions at the same time.|`and(columnValue("City", equalTo("New York")), columnValue("Salary", greaterThan(10000)))`
|`or`|Used to evaluate for at least one matching condition.|`or(columnValue("City", equalTo("New York")), columnValue("Salary", greaterThan(10000)))`
|======