This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 5137fe26 feat(c/driver/postgresql,c/driver/sqlite): Implement FOREIGN
KEY constraints (#1099)
5137fe26 is described below
commit 5137fe26c4d034b74e91cb368b1207c719c32a31
Author: OleMussmann <[email protected]>
AuthorDate: Thu Oct 5 00:22:29 2023 +0200
feat(c/driver/postgresql,c/driver/sqlite): Implement FOREIGN KEY
constraints (#1099)
# Test and Implement Foreign Keys
This feature relies on Composite Primary Keys, which are implemented,
but had no tests. The missing tests are included in this PR as a
separate commit. The pull request closes #936 .
## Discussion points
- SQLite constraints are currently nameless, whereas the PostgreSQL
constraints have generated names. Should it stay that way?
- SQLite and PostgreSQL return their constraints in different orders. Is
that by design?
- Since the order of constraints can vary between DBs, the Foreign Key
tests currently identify constraint columns by their number of column
usages. Maybe there's a better solution to it?
- `c/validation/adbc_validation.cc` lines [1252-1255] assume a certain
order of the constraint column usages. Should we find an order-agnostic
solution instead? It might be more verbose. It's currently:
```
ForeignKeyColumnUsagesTest(child_constraint, 0, "adbc_fkey_parent_2_test",
"id_primary_col1");
ForeignKeyColumnUsagesTest(child_constraint, 1, "adbc_fkey_parent_2_test",
"id_primary_col2");
```
- `c/validation/adbc_validation.cc` lines [964,1115] assume a certain
order of the constraint column names. It is currently:
```
for (column_index = 0; column_index < number_of_columns; column_index++) {
std::string_view constraint_column_name(
constraint->constraint_column_names[column_index].data,
constraint->constraint_column_names[column_index].size_bytes);
ASSERT_EQ(constraint_column_name, columns[column_index]);
}
```
An order-agnostic, but less specific, approach to test this could be
```
ASSERT_THAT(constraint_column_name,
::testing::AnyOf(::testing::Eq(parent_2_column_names[0]),
::testing::Eq(parent_2_column_names[1])));
```
## Working tests for Composite Primary Keys
The composite primary key tests fail correctly in the following ways.
### Number of table columns
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1046: Failure
Expected equality of these values:
composite_table->n_table_columns
Which is: 9
2
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (87 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1046: Failure
Expected equality of these values:
composite_table->n_table_columns
Which is: 9
2
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
### Table names
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1057: Failure
Expected: (parent_2_column) != (nullptr), actual: NULL vs (nullptr)
could not find column NOT_EXISTING on adbc_composite_pkey_test table
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (90 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1057: Failure
Expected: (parent_2_column) != (nullptr), actual: NULL vs (nullptr)
could not find column NOT_EXISTING on adbc_composite_pkey_test table
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
### Constraint column names
This test gives less helpful output, but is aligned with the existing
primary key tests.
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1064: Failure
Expected equality of these values:
constraint_column_name
Which is: "id_primary_col1"
parent_2_column_names[column_name_index]
Which is: 0x55de7b
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (79 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1064: Failure
Expected equality of these values:
constraint_column_name
Which is: "id_primary_col1"
parent_2_column_names[column_name_index]
Which is: 0x512e86
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
### Number of table constraints
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1068: Failure
Expected equality of these values:
composite_table->n_table_constraints
Which is: 9
1
expected 1 constraint on adbc_pkey_test table, found: 9
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (87 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1068: Failure
Expected equality of these values:
composite_table->n_table_constraints
Which is: 9
1
expected 1 constraint on adbc_pkey_test table, found: 9
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
### Constraint type
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1075: Failure
Expected equality of these values:
composite_constraint_type
Which is: "UNKNOWN KEY"
"PRIMARY KEY"
Which is: 0x55dee9
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (87 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1075: Failure
Expected equality of these values:
composite_constraint_type
Which is: "UNKNOWN KEY"
"PRIMARY KEY"
Which is: 0x512ef4
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
### Number of constraints
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1076: Failure
Expected equality of these values:
composite_constraint->n_column_names
Which is: 9
2
expected constraint adbc_pkey_test_pkey to be applied to 2 columns, found: 9
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsPrimaryKey (79 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1076: Failure
Expected equality of these values:
composite_constraint->n_column_names
Which is: 9
2
expected constraint adbc_pkey_test_pkey to be applied to 2 columns, found: 9
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsPrimaryKey (0 ms)
```
## Working tests for Foreign Keys
The foreign key tests fail correctly in the following ways.
### Number of columns
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1229: Failure
Expected equality of these values:
child_table->n_table_columns
Which is: 9
3
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (103 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1229: Failure
Expected equality of these values:
child_table->n_table_columns
Which is: 9
3
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Table names
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1237: Failure
Expected: (child_column) != (nullptr), actual: NULL vs (nullptr)
could not find column NOT_EXISTING on adbc_fkey_child_test table
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (120 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1237: Failure
Expected: (child_column) != (nullptr), actual: NULL vs (nullptr)
could not find column NOT_EXISTING on adbc_fkey_child_test table
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Number of table constraints
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1244: Failure
Expected equality of these values:
child_table->n_table_constraints
Which is: 9
3
expected 3 constraint on adbc_fkey_child_test table, found: 9
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (98 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1244: Failure
Expected equality of these values:
child_table->n_table_constraints
Which is: 9
3
expected 3 constraint on adbc_fkey_child_test table, found: 9
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Constraint Type
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1095: Failure
Expected equality of these values:
constraint_type
Which is: "UNKNOWN KEY"
key_type
Which is: "FOREIGN KEY"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (130 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1095: Failure
Expected equality of these values:
constraint_type
Which is: "UNKNOWN KEY"
key_type
Which is: "FOREIGN KEY"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Constraint Column Names (less helpful, but aligned with primary key
tests)
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1106: Failure
Expected equality of these values:
constraint_column_name
Which is: "WRONG_NAME"
columns[column_index]
Which is: "id_child_col1"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (97 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1106: Failure
Expected equality of these values:
constraint_column_name
Which is: "WRONG_NAME"
columns[column_index]
Which is: "id_child_col1"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Target table name
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1139: Failure
Expected equality of these values:
constraint_column_usage_fk_table
Which is: "WRONG_NAME"
fk_table_name
Which is: "adbc_fkey_parent_2_test"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (103 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1139: Failure
Expected equality of these values:
constraint_column_usage_fk_table
Which is: "WRONG_NAME"
fk_table_name
Which is: "adbc_fkey_parent_2_test"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (103 ms)
```
### Target column name
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1146: Failure
Expected equality of these values:
constraint_column_usage_fk_column_name
Which is: "WRONG_NAME"
fk_column_name
Which is: "id_primary_col1"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (99 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1146: Failure
Expected equality of these values:
constraint_column_usage_fk_column_name
Which is: "WRONG_NAME"
fk_column_name
Which is: "id_primary_col1"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Skipped a mandatory constraint test
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1293: Failure
Value of: TestedConstraints.adbc_fkey_child_test_pkey
Actual: false
Expected: true
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (98 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1293: Failure
Value of: TestedConstraints.adbc_fkey_child_test_pkey
Actual: false
Expected: true
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Number of Foreign Key columns
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1096: Failure
Expected equality of these values:
constraint->n_column_names
Which is: 9
2
expected constraint FOREIGN KEY of adbc_fkey_child_test to be applied to 2
column(s), found: 9
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (102 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1096: Failure
Expected equality of these values:
constraint->n_column_names
Which is: 9
1
expected constraint PRIMARY KEY of adbc_fkey_child_test to be applied to 1
column(s), found: 9
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Foreign Key catalog name
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1120: Failure
Value of: constraint_column_usage_fk_catalog
Expected: (is equal to "main") or (is equal to "postgres")
Actual: "WRONG_NAME"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (99 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1120: Failure
Value of: constraint_column_usage_fk_catalog
Expected: (is equal to "main") or (is equal to "postgres")
Actual: "WRONG_NAME"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
### Foreign Key DB schema name
```
[ RUN ] PostgresConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1131: Failure
Value of: constraint_column_usage_fk_db_schema
Expected: (is equal to "") or (is equal to "public")
Actual: "WRONG_NAME"
[ FAILED ] PostgresConnectionTest.MetadataGetObjectsForeignKey (97 ms)
```
```
[ RUN ] SqliteConnectionTest.MetadataGetObjectsForeignKey
/home/ole/code/arrow-adbc/c/validation/adbc_validation.cc:1131: Failure
Value of: constraint_column_usage_fk_db_schema
Expected: (is equal to "") or (is equal to "public")
Actual: "WRONG_NAME"
[ FAILED ] SqliteConnectionTest.MetadataGetObjectsForeignKey (0 ms)
```
---------
Co-authored-by: Ole Mussmann <[email protected]>
Co-authored-by: Suvayu Ali <[email protected]>
---
c/driver/postgresql/postgresql_test.cc | 26 +++
c/driver/sqlite/sqlite.c | 51 +++---
c/driver/sqlite/sqlite_test.cc | 26 +++
c/validation/adbc_validation.cc | 283 ++++++++++++++++++++++++++++++---
c/validation/adbc_validation.h | 28 ++++
5 files changed, 370 insertions(+), 44 deletions(-)
diff --git a/c/driver/postgresql/postgresql_test.cc
b/c/driver/postgresql/postgresql_test.cc
index 2afc4dbb..303cc059 100644
--- a/c/driver/postgresql/postgresql_test.cc
+++ b/c/driver/postgresql/postgresql_test.cc
@@ -107,6 +107,32 @@ class PostgresQuirks : public
adbc_validation::DriverQuirks {
return ddl;
}
+ std::optional<std::string> CompositePrimaryKeyTableDdl(
+ std::string_view name) const override {
+ std::string ddl = "CREATE TABLE ";
+ ddl += name;
+ ddl += " (id_primary_col1 SERIAL, id_primary_col2 SERIAL,";
+ ddl += " PRIMARY KEY (id_primary_col1, id_primary_col2));";
+ return ddl;
+ }
+
+ std::optional<std::string> ForeignKeyChildTableDdl(
+ std::string_view child_name, std::string_view parent_name_1,
+ std::string_view parent_name_2) const override {
+ std::string ddl = "CREATE TABLE ";
+ ddl += child_name;
+ ddl += " (id_child_col1 SERIAL PRIMARY KEY, ";
+ ddl += " id_child_col2 SERIAL, ";
+ ddl += " id_child_col3 SERIAL, ";
+ ddl += " FOREIGN KEY (id_child_col3) REFERENCES ";
+ ddl += parent_name_1;
+ ddl += " (id),";
+ ddl += " FOREIGN KEY (id_child_col1, id_child_col2) REFERENCES ";
+ ddl += parent_name_2;
+ ddl += " (id_primary_col1, id_primary_col2))";
+ return ddl;
+ }
+
std::string catalog() const override { return "postgres"; }
std::string db_schema() const override { return "public"; }
diff --git a/c/driver/sqlite/sqlite.c b/c/driver/sqlite/sqlite.c
index b47336fe..05243c03 100644
--- a/c/driver/sqlite/sqlite.c
+++ b/c/driver/sqlite/sqlite.c
@@ -573,12 +573,9 @@ AdbcStatusCode SqliteConnectionGetConstraintsImpl(
const char* from_col = (const char*)sqlite3_column_text(fk_stmt, 3);
const char* to_col = (const char*)sqlite3_column_text(fk_stmt, 4);
+ // New foreign key constraint or -constraint sets
if (fk_id != prev_fk_id) {
- CHECK_NA(INTERNAL, ArrowArrayAppendNull(constraint_name_col, 1), error);
- CHECK_NA(INTERNAL,
- ArrowArrayAppendString(constraint_name_col,
ArrowCharView("FOREIGN KEY")),
- error);
-
+ // Not first constraint of the table
if (prev_fk_id != -1) {
CHECK_NA(INTERNAL,
ArrowArrayFinishElement(constraint_column_names_col), error);
CHECK_NA(INTERNAL,
ArrowArrayFinishElement(constraint_column_usage_col), error);
@@ -586,28 +583,34 @@ AdbcStatusCode SqliteConnectionGetConstraintsImpl(
}
prev_fk_id = fk_id;
+ CHECK_NA(INTERNAL, ArrowArrayAppendNull(constraint_name_col, 1), error);
CHECK_NA(INTERNAL,
- ArrowArrayAppendString(
- constraint_column_names_items,
- (struct ArrowStringView){
- .data = from_col, .size_bytes =
sqlite3_column_bytes(pk_stmt, 3)}),
- error);
- CHECK_NA(INTERNAL, ArrowArrayAppendString(fk_catalog_col,
ArrowCharView("main")),
- error);
- CHECK_NA(INTERNAL, ArrowArrayAppendNull(fk_db_schema_col, 1), error);
- CHECK_NA(INTERNAL,
- ArrowArrayAppendString(
- fk_table_col,
- (struct ArrowStringView){
- .data = to_table, .size_bytes =
sqlite3_column_bytes(pk_stmt, 2)}),
- error);
- CHECK_NA(INTERNAL,
- ArrowArrayAppendString(
- fk_column_name_col,
- (struct ArrowStringView){
- .data = to_col, .size_bytes =
sqlite3_column_bytes(pk_stmt, 4)}),
+ ArrowArrayAppendString(constraint_type_col,
ArrowCharView("FOREIGN KEY")),
error);
}
+ CHECK_NA(INTERNAL,
+ ArrowArrayAppendString(
+ constraint_column_names_items,
+ (struct ArrowStringView){
+ .data = from_col, .size_bytes =
sqlite3_column_bytes(fk_stmt, 3)}),
+ error);
+ CHECK_NA(INTERNAL, ArrowArrayAppendString(fk_catalog_col,
ArrowCharView("main")),
+ error);
+ CHECK_NA(INTERNAL, ArrowArrayAppendNull(fk_db_schema_col, 1), error);
+ CHECK_NA(INTERNAL,
+ ArrowArrayAppendString(
+ fk_table_col,
+ (struct ArrowStringView){
+ .data = to_table, .size_bytes =
sqlite3_column_bytes(fk_stmt, 2)}),
+ error);
+ CHECK_NA(INTERNAL,
+ ArrowArrayAppendString(
+ fk_column_name_col,
+ (struct ArrowStringView){
+ .data = to_col, .size_bytes =
sqlite3_column_bytes(fk_stmt, 4)}),
+ error);
+
+ CHECK_NA(INTERNAL, ArrowArrayFinishElement(constraint_column_usage_items),
error);
}
RAISE(INTERNAL, rc == SQLITE_DONE, sqlite3_errmsg(conn->conn), error);
if (prev_fk_id != -1) {
diff --git a/c/driver/sqlite/sqlite_test.cc b/c/driver/sqlite/sqlite_test.cc
index 617fcb01..e5566df2 100644
--- a/c/driver/sqlite/sqlite_test.cc
+++ b/c/driver/sqlite/sqlite_test.cc
@@ -98,6 +98,32 @@ class SqliteQuirks : public adbc_validation::DriverQuirks {
return ddl;
}
+ std::optional<std::string> CompositePrimaryKeyTableDdl(
+ std::string_view name) const override {
+ std::string ddl = "CREATE TABLE ";
+ ddl += name;
+ ddl += " (id_primary_col1 INTEGER, id_primary_col2 INTEGER,";
+ ddl += " PRIMARY KEY (id_primary_col1, id_primary_col2));";
+ return ddl;
+ }
+
+ std::optional<std::string> ForeignKeyChildTableDdl(
+ std::string_view child_name, std::string_view parent_name_1,
+ std::string_view parent_name_2) const override {
+ std::string ddl = "CREATE TABLE ";
+ ddl += child_name;
+ ddl += " (id_child_col1 INTEGER PRIMARY KEY,";
+ ddl += " id_child_col2 INTEGER,";
+ ddl += " id_child_col3 INTEGER,";
+ ddl += " FOREIGN KEY (id_child_col3) REFERENCES ";
+ ddl += parent_name_1;
+ ddl += " (id),";
+ ddl += " FOREIGN KEY (id_child_col1, id_child_col2) REFERENCES ";
+ ddl += parent_name_2;
+ ddl += " (id_primary_col1, id_primary_col2));";
+ return ddl;
+ }
+
bool supports_bulk_ingest(const char* mode) const override {
return std::strcmp(mode, ADBC_INGEST_OPTION_MODE_APPEND) == 0 ||
std::strcmp(mode, ADBC_INGEST_OPTION_MODE_CREATE) == 0;
diff --git a/c/validation/adbc_validation.cc b/c/validation/adbc_validation.cc
index 0dedb8f6..ab74f57c 100644
--- a/c/validation/adbc_validation.cc
+++ b/c/validation/adbc_validation.cc
@@ -945,6 +945,58 @@ void ConnectionTest::TestMetadataGetObjectsConstraints() {
// TODO: can't be done portably (need to create tables with primary keys and
such)
}
+void ConstraintTest(const AdbcGetObjectsConstraint* constraint,
+ const std::string& key_type,
+ const std::vector<std::string>& columns) {
+ std::string_view constraint_type(constraint->constraint_type.data,
+ constraint->constraint_type.size_bytes);
+ int number_of_columns = columns.size();
+ ASSERT_EQ(constraint_type, key_type);
+ ASSERT_EQ(constraint->n_column_names, number_of_columns)
+ << "expected constraint " << key_type
+ << " of adbc_fkey_child_test to be applied to " <<
std::to_string(number_of_columns)
+ << " column(s), found: " << constraint->n_column_names;
+
+ int column_index;
+ for (column_index = 0; column_index < number_of_columns; column_index++) {
+ std::string_view constraint_column_name(
+ constraint->constraint_column_names[column_index].data,
+ constraint->constraint_column_names[column_index].size_bytes);
+ ASSERT_EQ(constraint_column_name, columns[column_index]);
+ }
+}
+
+void ForeignKeyColumnUsagesTest(const AdbcGetObjectsConstraint* constraint,
+ const std::string& catalog, const std::string&
db_schema,
+ const int column_usage_index,
+ const std::string& fk_table_name,
+ const std::string& fk_column_name) {
+ // Test fk_catalog
+ std::string_view constraint_column_usage_fk_catalog(
+
constraint->constraint_column_usages[column_usage_index]->fk_catalog.data,
+
constraint->constraint_column_usages[column_usage_index]->fk_catalog.size_bytes);
+ ASSERT_THAT(constraint_column_usage_fk_catalog, catalog);
+
+ // Test fk_db_schema
+ std::string_view constraint_column_usage_fk_db_schema(
+
constraint->constraint_column_usages[column_usage_index]->fk_db_schema.data,
+
constraint->constraint_column_usages[column_usage_index]->fk_db_schema.size_bytes);
+ ASSERT_THAT(constraint_column_usage_fk_db_schema, db_schema);
+
+ // Test fk_table_name
+ std::string_view constraint_column_usage_fk_table(
+ constraint->constraint_column_usages[column_usage_index]->fk_table.data,
+
constraint->constraint_column_usages[column_usage_index]->fk_table.size_bytes);
+ ASSERT_EQ(constraint_column_usage_fk_table, fk_table_name);
+
+ // Test fk_column_name
+ std::string_view constraint_column_usage_fk_column_name(
+
constraint->constraint_column_usages[column_usage_index]->fk_column_name.data,
+ constraint->constraint_column_usages[column_usage_index]
+ ->fk_column_name.size_bytes);
+ ASSERT_EQ(constraint_column_usage_fk_column_name, fk_column_name);
+}
+
void ConnectionTest::TestMetadataGetObjectsPrimaryKey() {
ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error));
ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error),
IsOkStatus(&error));
@@ -953,6 +1005,7 @@ void ConnectionTest::TestMetadataGetObjectsPrimaryKey() {
GTEST_SKIP();
}
+ // Set up primary key ddl
std::optional<std::string> maybe_ddl =
quirks()->PrimaryKeyTableDdl("adbc_pkey_test");
if (!maybe_ddl.has_value()) {
GTEST_SKIP();
@@ -962,16 +1015,37 @@ void ConnectionTest::TestMetadataGetObjectsPrimaryKey() {
ASSERT_THAT(quirks()->DropTable(&connection, "adbc_pkey_test", &error),
IsOkStatus(&error));
+ // Set up composite primary key ddl
+ std::optional<std::string> maybe_composite_ddl =
+ quirks()->CompositePrimaryKeyTableDdl("adbc_composite_pkey_test");
+ if (!maybe_composite_ddl.has_value()) {
+ GTEST_SKIP();
+ }
+ std::string composite_ddl = std::move(*maybe_composite_ddl);
+
+ // Empty database
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_pkey_test", &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_composite_pkey_test",
&error),
+ IsOkStatus(&error));
+
+ // Populate database
{
- Handle<AdbcStatement> statement;
- ASSERT_THAT(AdbcStatementNew(&connection, &statement.value, &error),
- IsOkStatus(&error));
- ASSERT_THAT(AdbcStatementSetSqlQuery(&statement.value, ddl.c_str(),
&error),
- IsOkStatus(&error));
- int64_t rows_affected = 0;
- ASSERT_THAT(
- AdbcStatementExecuteQuery(&statement.value, nullptr, &rows_affected,
&error),
- IsOkStatus(&error));
+ Handle<AdbcStatement> statements[2];
+ std::string ddls[2] = {ddl, composite_ddl};
+ int64_t rows_affected;
+
+ for (int ddl_index = 0; ddl_index < 2; ddl_index++) {
+ rows_affected = 0;
+ ASSERT_THAT(AdbcStatementNew(&connection, &statements[ddl_index].value,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementSetSqlQuery(&statements[ddl_index].value,
+ ddls[ddl_index].c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementExecuteQuery(&statements[ddl_index].value,
nullptr,
+ &rows_affected, &error),
+ IsOkStatus(&error));
+ }
}
adbc_validation::StreamReader reader;
@@ -988,6 +1062,7 @@ void ConnectionTest::TestMetadataGetObjectsPrimaryKey() {
ASSERT_NE(*get_objects_data, nullptr)
<< "could not initialize the AdbcGetObjectsData object";
+ // Test primary key
struct AdbcGetObjectsTable* table =
AdbcGetObjectsDataGetTableByName(*get_objects_data,
quirks()->catalog().c_str(),
quirks()->db_schema().c_str(),
"adbc_pkey_test");
@@ -1004,18 +1079,186 @@ void
ConnectionTest::TestMetadataGetObjectsPrimaryKey() {
<< table->n_table_constraints;
struct AdbcGetObjectsConstraint* constraint = table->table_constraints[0];
+ ConstraintTest(constraint, "PRIMARY KEY", {"id"});
- std::string_view constraint_type(constraint->constraint_type.data,
- constraint->constraint_type.size_bytes);
- ASSERT_EQ(constraint_type, "PRIMARY KEY");
- ASSERT_EQ(constraint->n_column_names, 1)
- << "expected constraint adbc_pkey_test_pkey to be applied to 1 column,
found: "
- << constraint->n_column_names;
-
- std::string_view constraint_column_name(
- constraint->constraint_column_names[0].data,
- constraint->constraint_column_names[0].size_bytes);
- ASSERT_EQ(constraint_column_name, "id");
+ // Test composite primary key
+ struct AdbcGetObjectsTable* composite_table =
AdbcGetObjectsDataGetTableByName(
+ *get_objects_data, quirks()->catalog().c_str(),
quirks()->db_schema().c_str(),
+ "adbc_composite_pkey_test");
+ ASSERT_NE(composite_table, nullptr) << "could not find
adbc_composite_pkey_test table";
+
+ // The composite primary key table has two columns: id_primary_col1,
id_primary_col2
+ ASSERT_EQ(composite_table->n_table_columns, 2);
+
+ struct AdbcGetObjectsConstraint* composite_constraint =
+ composite_table->table_constraints[0];
+ const char* parent_2_column_names[2] = {"id_primary_col1",
"id_primary_col2"};
+ struct AdbcGetObjectsColumn* parent_2_column;
+ for (int column_name_index = 0; column_name_index < 2; column_name_index++) {
+ parent_2_column = AdbcGetObjectsDataGetColumnByName(
+ *get_objects_data, quirks()->catalog().c_str(),
quirks()->db_schema().c_str(),
+ "adbc_composite_pkey_test", parent_2_column_names[column_name_index]);
+ ASSERT_NE(parent_2_column, nullptr)
+ << "could not find column " << parent_2_column_names[column_name_index]
+ << " on adbc_composite_pkey_test table";
+
+ std::string_view constraint_column_name(
+ composite_constraint->constraint_column_names[column_name_index].data,
+
composite_constraint->constraint_column_names[column_name_index].size_bytes);
+ ASSERT_EQ(constraint_column_name,
parent_2_column_names[column_name_index]);
+ }
+
+ ConstraintTest(composite_constraint, "PRIMARY KEY",
+ {"id_primary_col1", "id_primary_col2"});
+}
+
+void ConnectionTest::TestMetadataGetObjectsForeignKey() {
+ ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error),
IsOkStatus(&error));
+
+ if (!quirks()->supports_get_objects()) {
+ GTEST_SKIP();
+ }
+
+ // Load DDLs
+ std::optional<std::string> maybe_parent_1_ddl =
+ quirks()->PrimaryKeyTableDdl("adbc_fkey_parent_1_test");
+ if (!maybe_parent_1_ddl.has_value()) {
+ GTEST_SKIP();
+ }
+
+ std::string parent_1_ddl = std::move(*maybe_parent_1_ddl);
+
+ std::optional<std::string> maybe_parent_2_ddl =
+ quirks()->CompositePrimaryKeyTableDdl("adbc_fkey_parent_2_test");
+ if (!maybe_parent_2_ddl.has_value()) {
+ GTEST_SKIP();
+ }
+ std::string parent_2_ddl = std::move(*maybe_parent_2_ddl);
+
+ std::optional<std::string> maybe_child_ddl =
quirks()->ForeignKeyChildTableDdl(
+ "adbc_fkey_child_test", "adbc_fkey_parent_1_test",
"adbc_fkey_parent_2_test");
+ if (!maybe_child_ddl.has_value()) {
+ GTEST_SKIP();
+ }
+ std::string child_ddl = std::move(*maybe_child_ddl);
+
+ // Empty database
+ // First drop the child table, since the parent tables depends on it
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_fkey_child_test", &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_fkey_parent_1_test",
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_fkey_parent_2_test",
&error),
+ IsOkStatus(&error));
+
+ // Populate database
+ {
+ Handle<AdbcStatement> statements[3];
+ std::string ddls[3] = {parent_1_ddl, parent_2_ddl, child_ddl};
+ int64_t rows_affected;
+
+ for (int ddl_index = 0; ddl_index < 3; ddl_index++) {
+ rows_affected = 0;
+ ASSERT_THAT(AdbcStatementNew(&connection, &statements[ddl_index].value,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementSetSqlQuery(&statements[ddl_index].value,
+ ddls[ddl_index].c_str(), &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementExecuteQuery(&statements[ddl_index].value,
nullptr,
+ &rows_affected, &error),
+ IsOkStatus(&error));
+ }
+ }
+
+ adbc_validation::StreamReader reader;
+ ASSERT_THAT(
+ AdbcConnectionGetObjects(&connection, ADBC_OBJECT_DEPTH_ALL, nullptr,
nullptr,
+ nullptr, nullptr, nullptr,
&reader.stream.value, &error),
+ IsOkStatus(&error));
+ ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_NE(nullptr, reader.array->release);
+ ASSERT_GT(reader.array->length, 0);
+
+ auto get_objects_data =
adbc_validation::GetObjectsReader{&reader.array_view.value};
+ ASSERT_NE(*get_objects_data, nullptr)
+ << "could not initialize the AdbcGetObjectsData object";
+
+ // Test child table
+ struct AdbcGetObjectsTable* child_table = AdbcGetObjectsDataGetTableByName(
+ *get_objects_data, quirks()->catalog().c_str(),
quirks()->db_schema().c_str(),
+ "adbc_fkey_child_test");
+ ASSERT_NE(child_table, nullptr) << "could not find adbc_fkey_child_test
table";
+
+ // The child table has three columns: id_child_col1, id_child_col2,
id_child_col3
+ ASSERT_EQ(child_table->n_table_columns, 3);
+
+ const char* child_column_names[3] = {"id_child_col1", "id_child_col2",
"id_child_col3"};
+ struct AdbcGetObjectsColumn* child_column;
+ for (int column_index = 0; column_index < 2; column_index++) {
+ child_column = AdbcGetObjectsDataGetColumnByName(
+ *get_objects_data, quirks()->catalog().c_str(),
quirks()->db_schema().c_str(),
+ "adbc_fkey_child_test", child_column_names[column_index]);
+ ASSERT_NE(child_column, nullptr)
+ << "could not find column " << child_column_names[column_index]
+ << " on adbc_fkey_child_test table";
+ }
+
+ // There are three constraints: PRIMARY KEY, FOREIGN KEY, FOREIGN KEY
+ // affecting one, one, and two columns, respetively
+ ASSERT_EQ(child_table->n_table_constraints, 3)
+ << "expected 3 constraint on adbc_fkey_child_test table, found: "
+ << child_table->n_table_constraints;
+
+ struct ConstraintFlags {
+ bool adbc_fkey_child_test_pkey = false;
+ bool adbc_fkey_child_test_id_child_col3_fkey = false;
+ bool adbc_fkey_child_test_id_child_col1_id_child_col2_fkey = false;
+ };
+ ConstraintFlags TestedConstraints;
+
+ for (int constraint_index = 0; constraint_index < 3; constraint_index++) {
+ struct AdbcGetObjectsConstraint* child_constraint =
+ child_table->table_constraints[constraint_index];
+ int numbern_of_column_usages = child_constraint->n_column_usages;
+
+ // The number of column usages identifies the constraint
+ switch (numbern_of_column_usages) {
+ case 0: {
+ // adbc_fkey_child_test_pkey
+ ConstraintTest(child_constraint, "PRIMARY KEY", {"id_child_col1"});
+
+ TestedConstraints.adbc_fkey_child_test_pkey = true;
+ } break;
+ case 1: {
+ // adbc_fkey_child_test_id_child_col3_fkey
+ ConstraintTest(child_constraint, "FOREIGN KEY", {"id_child_col3"});
+ ForeignKeyColumnUsagesTest(child_constraint, quirks()->catalog(),
+ quirks()->db_schema(), 0,
"adbc_fkey_parent_1_test",
+ "id");
+
+ TestedConstraints.adbc_fkey_child_test_id_child_col3_fkey = true;
+ } break;
+ case 2: {
+ // adbc_fkey_child_test_id_child_col1_id_child_col2_fkey
+ ConstraintTest(child_constraint, "FOREIGN KEY",
+ {"id_child_col1", "id_child_col2"});
+ ForeignKeyColumnUsagesTest(child_constraint, quirks()->catalog(),
+ quirks()->db_schema(), 0,
"adbc_fkey_parent_2_test",
+ "id_primary_col1");
+ ForeignKeyColumnUsagesTest(child_constraint, quirks()->catalog(),
+ quirks()->db_schema(), 1,
"adbc_fkey_parent_2_test",
+ "id_primary_col2");
+
+
TestedConstraints.adbc_fkey_child_test_id_child_col1_id_child_col2_fkey = true;
+ } break;
+ }
+ }
+
+ ASSERT_TRUE(TestedConstraints.adbc_fkey_child_test_pkey);
+ ASSERT_TRUE(TestedConstraints.adbc_fkey_child_test_id_child_col3_fkey);
+
ASSERT_TRUE(TestedConstraints.adbc_fkey_child_test_id_child_col1_id_child_col2_fkey);
}
void ConnectionTest::TestMetadataGetObjectsCancel() {
diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h
index df5e17ff..e5b31e02 100644
--- a/c/validation/adbc_validation.h
+++ b/c/validation/adbc_validation.h
@@ -86,6 +86,32 @@ class DriverQuirks {
return std::nullopt;
}
+ /// \brief Get the statement to create a table with a composite primary key,
+ /// or nullopt if not supported.
+ ///
+ /// The table should have two columns:
+ /// - "id_primary_col1" of Arrow type int64 (together forming a composite
primary key)
+ /// - "id_primary_col2" of Arrow type int64 (together forming a composite
primary key)
+ virtual std::optional<std::string> CompositePrimaryKeyTableDdl(
+ std::string_view name) const {
+ return std::nullopt;
+ }
+
+ /// \brief Get the statement to create a child table with foreign keys,
+ /// or nullopt if not supported.
+ ///
+ /// The child table should have three columns:
+ /// - "id_child_col1" of Arrow type int64 (primary key, foreign key)
referencing "id" in
+ /// the parent 1 primary key table
+ /// - "id_child_col2" of Arrow type int64 (composite foreign key) together
with:
+ /// - "id_child_col3" of Arrow type int64 (composite foreign key) referencing
+ /// "(id_primary_col1, id_primary_col2)" in the parent 2 primary key table
+ virtual std::optional<std::string> ForeignKeyChildTableDdl(
+ std::string_view child_name, std::string_view parent_name_1,
+ std::string_view parent_name_2) const {
+ return std::nullopt;
+ }
+
/// \brief Return the SQL to reference the bind parameter of the given index
virtual std::string BindParameter(int index) const { return "?"; }
@@ -215,6 +241,7 @@ class ConnectionTest {
void TestMetadataGetObjectsColumns();
void TestMetadataGetObjectsConstraints();
void TestMetadataGetObjectsPrimaryKey();
+ void TestMetadataGetObjectsForeignKey();
void TestMetadataGetObjectsCancel();
void TestMetadataGetStatisticNames();
@@ -255,6 +282,7 @@ class ConnectionTest {
TestMetadataGetObjectsConstraints();
\
}
\
TEST_F(FIXTURE, MetadataGetObjectsPrimaryKey) {
TestMetadataGetObjectsPrimaryKey(); } \
+ TEST_F(FIXTURE, MetadataGetObjectsForeignKey) {
TestMetadataGetObjectsForeignKey(); } \
TEST_F(FIXTURE, MetadataGetObjectsCancel) { TestMetadataGetObjectsCancel();
} \
TEST_F(FIXTURE, MetadataGetStatisticNames) {
TestMetadataGetStatisticNames(); }