Skip to content

Commit 9b6bd50

Browse files
committed
test: add null primary key rejection tests for SQLite and PostgreSQL
Verify that INSERT operations with NULL primary key values are rejected with an appropriate error message, even when the column lacks an explicit NOT NULL constraint.
1 parent 7c3e4bf commit 9b6bd50

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
-- Test: NULL Primary Key Insert Rejection
2+
-- Verifies that inserting a NULL primary key into a cloudsync-enabled table fails
3+
-- and that the metatable only contains rows for valid inserts.
4+
5+
\set testid '30'
6+
\ir helper_test_init.sql
7+
8+
\connect postgres
9+
\ir helper_psql_conn_setup.sql
10+
11+
-- Cleanup and create test database
12+
DROP DATABASE IF EXISTS cloudsync_test_30;
13+
CREATE DATABASE cloudsync_test_30;
14+
15+
\connect cloudsync_test_30
16+
\ir helper_psql_conn_setup.sql
17+
CREATE EXTENSION IF NOT EXISTS cloudsync;
18+
19+
-- Create table with primary key and init cloudsync
20+
CREATE TABLE t_null_pk (
21+
id TEXT NOT NULL PRIMARY KEY,
22+
value TEXT
23+
);
24+
25+
SELECT cloudsync_init('t_null_pk', 'CLS', true) AS _init \gset
26+
27+
-- Test 1: INSERT with NULL primary key should fail
28+
DO $$
29+
BEGIN
30+
INSERT INTO t_null_pk (id, value) VALUES (NULL, 'test');
31+
RAISE EXCEPTION 'INSERT with NULL PK should have failed';
32+
EXCEPTION WHEN not_null_violation THEN
33+
-- Expected
34+
END $$;
35+
36+
SELECT (COUNT(*) = 0) AS null_pk_rejected FROM t_null_pk \gset
37+
\if :null_pk_rejected
38+
\echo [PASS] (:testid) NULL PK insert rejected
39+
\else
40+
\echo [FAIL] (:testid) NULL PK insert was not rejected
41+
SELECT (:fail::int + 1) AS fail \gset
42+
\endif
43+
44+
-- Test 2: INSERT with valid (non-NULL) primary key should succeed
45+
INSERT INTO t_null_pk (id, value) VALUES ('valid_id', 'test');
46+
47+
SELECT (COUNT(*) = 1) AS valid_insert_ok FROM t_null_pk WHERE id = 'valid_id' \gset
48+
\if :valid_insert_ok
49+
\echo [PASS] (:testid) Valid PK insert succeeded
50+
\else
51+
\echo [FAIL] (:testid) Valid PK insert failed
52+
SELECT (:fail::int + 1) AS fail \gset
53+
\endif
54+
55+
-- Test 3: Metatable should have exactly 1 row (from the valid insert only)
56+
SELECT (COUNT(*) = 1) AS meta_row_ok FROM t_null_pk_cloudsync \gset
57+
\if :meta_row_ok
58+
\echo [PASS] (:testid) Metatable has exactly 1 row
59+
\else
60+
\echo [FAIL] (:testid) Metatable row count mismatch
61+
SELECT (:fail::int + 1) AS fail \gset
62+
\endif
63+
64+
-- Cleanup
65+
\ir helper_test_cleanup.sql
66+
\if :should_cleanup
67+
DROP DATABASE IF EXISTS cloudsync_test_30;
68+
\endif

test/postgresql/full_test.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
\ir 27_rls_batch_merge.sql
3838
\ir 28_db_version_tracking.sql
3939
\ir 29_rls_multicol.sql
40+
\ir 30_null_prikey_insert.sql
4041

4142
-- 'Test summary'
4243
\echo '\nTest summary:'

test/unit.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,6 +2017,43 @@ bool do_test_error_cases (sqlite3 *db) {
20172017
return true;
20182018
}
20192019

2020+
bool do_test_null_prikey_insert (sqlite3 *db) {
2021+
// Create a table with a primary key that allows NULL (no NOT NULL constraint)
2022+
const char *sql = "CREATE TABLE IF NOT EXISTS t_null_pk (id TEXT PRIMARY KEY, value TEXT);"
2023+
"SELECT cloudsync_init('t_null_pk');";
2024+
int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
2025+
if (rc != SQLITE_OK) return false;
2026+
2027+
// Attempt to insert a row with NULL primary key — should fail
2028+
char *errmsg = NULL;
2029+
sql = "INSERT INTO t_null_pk (id, value) VALUES (NULL, 'test');";
2030+
rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
2031+
if (rc == SQLITE_OK) return false; // should have failed
2032+
if (!errmsg) return false;
2033+
2034+
// Verify the error message matches the expected format
2035+
const char *expected = "Insert aborted because primary key in table t_null_pk contains NULL values.";
2036+
bool match = (strcmp(errmsg, expected) == 0);
2037+
sqlite3_free(errmsg);
2038+
if (!match) return false;
2039+
2040+
// Verify that a non-NULL primary key insert succeeds
2041+
sql = "INSERT INTO t_null_pk (id, value) VALUES ('valid_id', 'test');";
2042+
rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
2043+
if (rc != SQLITE_OK) return false;
2044+
2045+
// Verify the metatable has exactly 1 row (only the valid insert)
2046+
sqlite3_stmt *stmt = NULL;
2047+
rc = sqlite3_prepare_v2(db, "SELECT COUNT(*) FROM t_null_pk_cloudsync;", -1, &stmt, NULL);
2048+
if (rc != SQLITE_OK) return false;
2049+
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return false; }
2050+
int count = sqlite3_column_int(stmt, 0);
2051+
sqlite3_finalize(stmt);
2052+
if (count != 1) return false;
2053+
2054+
return true;
2055+
}
2056+
20202057
bool do_test_internal_functions (void) {
20212058
sqlite3 *db = NULL;
20222059
sqlite3_stmt *vm = NULL;
@@ -7842,6 +7879,7 @@ int main (int argc, const char * argv[]) {
78427879
result += test_report("DBUtils Test:", do_test_dbutils());
78437880
result += test_report("Minor Test:", do_test_others(db));
78447881
result += test_report("Test Error Cases:", do_test_error_cases(db));
7882+
result += test_report("Null PK Insert Test:", do_test_null_prikey_insert(db));
78457883
result += test_report("Test Single PK:", do_test_single_pk(print_result));
78467884

78477885
int test_mask = TEST_INSERT | TEST_UPDATE | TEST_DELETE;

0 commit comments

Comments
 (0)