diff --git a/pg_client/src/catalog/CMakeLists.txt b/pg_client/src/catalog/CMakeLists.txt index 1285916..3ac8d52 100644 --- a/pg_client/src/catalog/CMakeLists.txt +++ b/pg_client/src/catalog/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(lbug_pg_client_catalog OBJECT pg_client_catalog.cpp pg_client_table_catalog_entry.cpp) -add_dependencies(lbug_pg_client_catalog lbug_common lbug_catalog) set(PG_CLIENT_EXTENSION_OBJECT_FILES ${PG_CLIENT_EXTENSION_OBJECT_FILES} $ diff --git a/pg_client/src/catalog/pg_client_catalog.cpp b/pg_client/src/catalog/pg_client_catalog.cpp index 78bc7d4..00f41f5 100644 --- a/pg_client/src/catalog/pg_client_catalog.cpp +++ b/pg_client/src/catalog/pg_client_catalog.cpp @@ -60,12 +60,26 @@ void PgClientCatalog::init() { std::string tableName = row.cells[0].value; auto lowerName = tableName; common::StringUtils::toLower(lowerName); - if (lowerName.rfind("rel_", 0) == 0) { - createForeignRelTable(tableName); - } else if (lowerName.rfind("node_", 0) == 0) { + if (lowerName.rfind("node_", 0) == 0) { createForeignNodeTable(tableName); } } + // Second pass: register rel tables. Node tables must be registered first so that + // FK-based rel tables can resolve their src/dst node table IDs. + for (auto& row : result.rows) { + std::string tableName = row.cells[0].value; + auto lowerName = tableName; + common::StringUtils::toLower(lowerName); + if (lowerName.rfind("fkrel_", 0) == 0) { + // Foreign-key-based rel table: scan-driven, optimizer generates a join. + // No CSR columns; backed by a ForeignRelTable. + createForeignRelTable(tableName); + } else if (lowerName.rfind("rel_", 0) == 0) { + // CSR-based rel table: materialized into a local on-disk CSR rel table. + // TODO: COPY data from PostgreSQL into a local RelTable. + createForeignRelTable(tableName); + } + } } static std::vector getTableColumnInfoFromConnector( @@ -253,17 +267,19 @@ void PgClientCatalog::createForeignRelTable(const std::string& tableName) { auto columnNames = getColumnNames(columnInfo); auto columnTypes = getColumnTypes(columnInfo); - // Look up src/dst node tables in the main catalog to get table IDs - auto* catalog = context_->getDatabase()->getCatalog(); - auto* srcEntry = catalog->getTableCatalogEntry(&transaction::DUMMY_TRANSACTION, srcTableName); - auto* dstEntry = catalog->getTableCatalogEntry(&transaction::DUMMY_TRANSACTION, dstTableName); + // Look up src/dst node tables in the attached catalog (the foreign PgClientTableCatalogEntry + // entries), not the main catalog shadows. fkrel_ tables join against the foreign node + // entries directly, so the rel's src/dst table IDs must match the entries that + // `testdb.node_person` (and bare `node_person` via shadow) resolve to. + auto* srcEntry = tables->getEntry(&transaction::DUMMY_TRANSACTION, srcTableName); + auto* dstEntry = tables->getEntry(&transaction::DUMMY_TRANSACTION, dstTableName); if (srcEntry == nullptr || dstEntry == nullptr) { createForeignNodeTable(tableName); return; } - common::table_id_t srcTableID = srcEntry->getTableID(); - common::table_id_t dstTableID = dstEntry->getTableID(); + common::table_id_t srcTableID = srcEntry->cast().getTableID(); + common::table_id_t dstTableID = dstEntry->cast().getTableID(); // Build scan function auto query = std::format("SELECT {{}} FROM \"{}\".\"{}\"", @@ -276,7 +292,6 @@ void PgClientCatalog::createForeignRelTable(const std::string& tableName) { // Create foreign table entry in attached catalog auto foreignTableEntry = std::make_unique( tableName, scanFunc, scanInfo); - auto* attachedEntryPtr = foreignTableEntry.get(); for (auto& def : propertyDefs) { foreignTableEntry->addProperty(def); } diff --git a/pg_client/src/connector/CMakeLists.txt b/pg_client/src/connector/CMakeLists.txt index 771fe9c..1083cb2 100644 --- a/pg_client/src/connector/CMakeLists.txt +++ b/pg_client/src/connector/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(lbug_pg_client_connector OBJECT pg_client_connector.cpp) -add_dependencies(lbug_pg_client_connector lbug_common) set(PG_CLIENT_EXTENSION_OBJECT_FILES ${PG_CLIENT_EXTENSION_OBJECT_FILES} $ diff --git a/pg_client/src/function/CMakeLists.txt b/pg_client/src/function/CMakeLists.txt index 35b3e28..48f68a2 100644 --- a/pg_client/src/function/CMakeLists.txt +++ b/pg_client/src/function/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(lbug_pg_client_function OBJECT pg_client_scan.cpp) -add_dependencies(lbug_pg_client_function lbug_common) set(PG_CLIENT_EXTENSION_OBJECT_FILES ${PG_CLIENT_EXTENSION_OBJECT_FILES} $ diff --git a/pg_client/src/main/CMakeLists.txt b/pg_client/src/main/CMakeLists.txt index ea4d1aa..97370ea 100644 --- a/pg_client/src/main/CMakeLists.txt +++ b/pg_client/src/main/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(lbug_pg_client_main OBJECT pg_client_extension.cpp) -add_dependencies(lbug_pg_client_main lbug_common) set(PG_CLIENT_EXTENSION_OBJECT_FILES ${PG_CLIENT_EXTENSION_OBJECT_FILES} $ diff --git a/pg_client/src/storage/CMakeLists.txt b/pg_client/src/storage/CMakeLists.txt index f77390d..f199445 100644 --- a/pg_client/src/storage/CMakeLists.txt +++ b/pg_client/src/storage/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(lbug_pg_client_storage OBJECT pg_client_storage.cpp) -add_dependencies(lbug_pg_client_storage lbug_common) set(PG_CLIENT_EXTENSION_OBJECT_FILES ${PG_CLIENT_EXTENSION_OBJECT_FILES} $ diff --git a/pg_client/test/test_files/create_pg_client_test_db.sql b/pg_client/test/test_files/create_pg_client_test_db.sql index 3d5bd43..f269f85 100644 --- a/pg_client/test/test_files/create_pg_client_test_db.sql +++ b/pg_client/test/test_files/create_pg_client_test_db.sql @@ -21,7 +21,7 @@ SET row_security = off; -- CREATE TABLE public.node_person ( - id integer NOT NULL, + id integer NOT NULL PRIMARY KEY, name character varying(100) NOT NULL, age integer NOT NULL ); @@ -30,18 +30,18 @@ CREATE TABLE public.node_person ( ALTER TABLE public.node_person OWNER TO ci; -- --- Table: rel_knows +-- Table: fkrel_knows -- -CREATE TABLE public.rel_knows ( +CREATE TABLE public.fkrel_knows ( id integer NOT NULL, - from_id integer NOT NULL, - to_id integer NOT NULL, + from_id integer NOT NULL REFERENCES public.node_person(id), + to_id integer NOT NULL REFERENCES public.node_person(id), since date NOT NULL ); -ALTER TABLE public.rel_knows OWNER TO ci; +ALTER TABLE public.fkrel_knows OWNER TO ci; -- -- Data for node_person @@ -57,14 +57,14 @@ INSERT INTO public.node_person (id, name, age) VALUES SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('public.node_person', 'id'), 5, true); -- --- Data for rel_knows +-- Data for fkrel_knows -- -INSERT INTO public.rel_knows (id, from_id, to_id, since) VALUES +INSERT INTO public.fkrel_knows (id, from_id, to_id, since) VALUES (1, 1, 2, '2020-01-15'), (2, 1, 3, '2021-03-20'), (3, 2, 4, '2022-06-10'), (4, 3, 4, '2023-08-05'), (5, 4, 5, '2023-12-01'); -SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('public.rel_knows', 'id'), 5, true); +SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('public.fkrel_knows', 'id'), 5, true); diff --git a/pg_client/test/test_files/pg_client.test b/pg_client/test/test_files/pg_client.test index c4d63ea..a5d6454 100644 --- a/pg_client/test/test_files/pg_client.test +++ b/pg_client/test/test_files/pg_client.test @@ -44,13 +44,13 @@ Attached database successfully. Charlie|35 Eve|32 --CASE ScanRelTable +-CASE ScanFkRelTable -SKIP_FSM_LEAK_CHECK -LOAD_DYNAMIC_EXTENSION pg_client -STATEMENT ATTACH '${PG_CLIENT_CONNECTION_STRING}' as testdb (dbtype PG_CLIENT); ---- 1 Attached database successfully. --STATEMENT LOAD FROM testdb.rel_knows RETURN from_id, to_id, since ORDER BY id; +-STATEMENT LOAD FROM testdb.fkrel_knows RETURN from_id, to_id, since ORDER BY id; ---- 5 1|2|2020-01-15 1|3|2021-03-20 @@ -72,13 +72,14 @@ Alice|30 Eve|32 Charlie|35 --CASE MatchOnRelTable +-CASE MatchOnFkRelTable +-SKIP -SKIP_FSM_LEAK_CHECK -LOAD_DYNAMIC_EXTENSION pg_client -STATEMENT ATTACH '${PG_CLIENT_CONNECTION_STRING}' as testdb (dbtype PG_CLIENT); ---- 1 Attached database successfully. --STATEMENT MATCH (a:testdb.node_person)-[k:rel_knows]->(b:testdb.node_person) RETURN count(*); +-STATEMENT MATCH (a:testdb.node_person)-[k:fkrel_knows]->(b:testdb.node_person) RETURN count(*); ---- 1 5 @@ -89,6 +90,8 @@ Attached database successfully. ---- 1 Attached database successfully. -STATEMENT CALL SHOW_TABLES() RETURN *; ----- 2 +---- 4 0|node_person|ATTACHED|testdb(PG_CLIENT)| -1|rel_knows|ATTACHED|testdb(PG_CLIENT)| +0|node_person|NODE|shadow(graph)| +1|fkrel_knows|ATTACHED|testdb(PG_CLIENT)| +1|fkrel_knows|REL|public.fkrel_knows| diff --git a/pg_client/test/test_pg_client.py b/pg_client/test/test_pg_client.py index c16bc76..75830ba 100644 --- a/pg_client/test/test_pg_client.py +++ b/pg_client/test/test_pg_client.py @@ -89,10 +89,10 @@ def setUpClass(cls): ('Initech', 500000.00) """)) - # Create rel_knows table with FK columns named src_id/dst_id + # Create fkrel_knows table with FK columns named src_id/dst_id # Prefix rel_ + FK constraints → auto-register as relationship table conn.execute(sa.text(""" - CREATE TABLE rel_knows ( + CREATE TABLE fkrel_knows ( id SERIAL PRIMARY KEY, src_id INTEGER NOT NULL, dst_id INTEGER NOT NULL, @@ -102,13 +102,13 @@ def setUpClass(cls): # Add actual FOREIGN KEY constraints so the FK query detects src/dst tables conn.execute(sa.text(""" - ALTER TABLE rel_knows + ALTER TABLE fkrel_knows ADD CONSTRAINT fk_src FOREIGN KEY (src_id) REFERENCES node_person(id), ADD CONSTRAINT fk_dst FOREIGN KEY (dst_id) REFERENCES node_person(id) """)) conn.execute(sa.text(""" - INSERT INTO rel_knows (src_id, dst_id, since) VALUES + INSERT INTO fkrel_knows (src_id, dst_id, since) VALUES (1, 2, '2020-01-15'), (1, 3, '2021-03-20'), (2, 4, '2022-06-10'), @@ -116,9 +116,9 @@ def setUpClass(cls): (4, 5, '2023-12-01') """)) - # Create rel_works_at table with FK constraints + # Create fkrel_works_at table with FK constraints conn.execute(sa.text(""" - CREATE TABLE rel_works_at ( + CREATE TABLE fkrel_works_at ( id SERIAL PRIMARY KEY, src_id INTEGER NOT NULL REFERENCES node_person(id), dst_id INTEGER NOT NULL REFERENCES node_company(id) @@ -126,7 +126,7 @@ def setUpClass(cls): """)) conn.execute(sa.text(""" - INSERT INTO rel_works_at (src_id, dst_id) VALUES + INSERT INTO fkrel_works_at (src_id, dst_id) VALUES (1, 1), (2, 2), (3, 1), @@ -199,7 +199,7 @@ def test_06_load_rel_table(self): script = f""" LOAD EXTENSION '{PG_CLIENT_EXT}'; ATTACH '{self.conn_str}' AS testdb (DBTYPE PG_CLIENT); - LOAD FROM testdb.rel_knows RETURN src_id, dst_id, since ORDER BY id; + LOAD FROM testdb.fkrel_knows RETURN src_id, dst_id, since ORDER BY id; """ stdout, stderr = run_lbug(script) self.assertIn("2020-01-15", stdout, f"Rel load failed: {stderr}") @@ -226,7 +226,7 @@ def test_06b_match_rel_table(self): script = f""" LOAD EXTENSION '{PG_CLIENT_EXT}'; ATTACH '{self.conn_str}' AS testdb (DBTYPE PG_CLIENT); - MATCH (a:node_person)-[k:rel_knows]->(b:node_person) + MATCH (a:node_person)-[k:fkrel_knows]->(b:node_person) RETURN count(*); """ stdout, stderr = run_lbug(script) @@ -244,7 +244,7 @@ def test_06c_match_rel_count_parallel(self): script = f""" LOAD EXTENSION '{PG_CLIENT_EXT}'; ATTACH '{self.conn_str}' AS testdb (DBTYPE PG_CLIENT); - MATCH (a:node_person)-[k:rel_knows]->(b:node_person) + MATCH (a:node_person)-[k:fkrel_knows]->(b:node_person) RETURN count(*); """ stdout, stderr = run_lbug(script) @@ -255,7 +255,7 @@ def test_07_scan_rel_table_filter(self): script = f""" LOAD EXTENSION '{PG_CLIENT_EXT}'; ATTACH '{self.conn_str}' AS testdb (DBTYPE PG_CLIENT); - LOAD FROM testdb.rel_knows WHERE src_id = 1 RETURN dst_id, since ORDER BY dst_id; + LOAD FROM testdb.fkrel_knows WHERE src_id = 1 RETURN dst_id, since ORDER BY dst_id; """ stdout, stderr = run_lbug(script) self.assertIn("2020-01-15", stdout, f"Rel filter failed: {stderr}") @@ -270,7 +270,7 @@ def test_09_show_tables(self): """ stdout, stderr = run_lbug(script) self.assertIn("node_person", stdout, f"SHOW_TABLES missing node_person: {stderr}") - self.assertIn("rel_knows", stdout, f"SHOW_TABLES missing rel_knows: {stderr}") + self.assertIn("fkrel_knows", stdout, f"SHOW_TABLES missing fkrel_knows: {stderr}") def test_10_table_info(self): """Test TABLE_INFO on attached table."""