Skip to content

Commit 79a5f0c

Browse files
emanzxclaude
andcommitted
fix(ddl): accept INT4/INT8/SMALLINT/INT2 in strict and kv schemas
`document_strict` and `kv` engines validate declared column types through `ColumnType::from_str`, which only recognized `BIGINT`/`INT64`/`INTEGER`/`INT`. `CREATE COLLECTION` with an `INT4`, `INT8`, `SMALLINT`, or `INT2` column was rejected as an unknown type even though these are valid PostgreSQL integer aliases. All of them now map to the same `Int64` storage variant, since these engines always store integers as a full i64 regardless of declared width. Fixes #223. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193ftf9LuUWejMqe2KVWkya
1 parent f7fcc77 commit 79a5f0c

3 files changed

Lines changed: 154 additions & 1 deletion

File tree

nodedb-types/src/columnar/column_parse.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,16 @@ impl FromStr for ColumnType {
121121
}
122122

123123
match upper.as_str() {
124-
"BIGINT" | "INT64" | "INTEGER" | "INT" => Ok(Self::Int64),
124+
// `INT4`/`INT8`/`SMALLINT`/`INT2` are PostgreSQL wire-width integer
125+
// keywords (issue #223: strict/kv `CREATE COLLECTION` rejected
126+
// them as unknown types even though they're valid aliases). They
127+
// all collapse to the same `Int64` storage variant as
128+
// `BIGINT`/`INTEGER`/`INT` — nodedb always stores integers as a
129+
// full i64; the declared width only narrows the advertised wire
130+
// OID (see `response_shape::schema::sql_data_type_to_ddl_col_type_with_raw`).
131+
"BIGINT" | "INT64" | "INTEGER" | "INT" | "INT4" | "INT8" | "SMALLINT" | "INT2" => {
132+
Ok(Self::Int64)
133+
}
125134
"FLOAT64" | "DOUBLE" | "REAL" | "FLOAT" => Ok(Self::Float64),
126135
"TEXT" | "STRING" | "VARCHAR" => Ok(Self::String),
127136
"BOOL" | "BOOLEAN" => Ok(Self::Bool),

nodedb-types/src/columnar/column_type.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,20 @@ mod tests {
286286
assert_eq!("UUID".parse::<ColumnType>().unwrap(), ColumnType::Uuid);
287287
}
288288

289+
/// `INT4`/`INT8`/`SMALLINT`/`INT2` are wire-width DDL keywords (issue
290+
/// #223: strict/kv `CREATE COLLECTION` rejected them as unknown column
291+
/// types even though they're valid PostgreSQL integer aliases). They all
292+
/// map to the same `Int64` storage variant as `BIGINT`/`INTEGER`/`INT` —
293+
/// nodedb's columnar/strict/kv storage always keeps integers as a full
294+
/// i64; only the wire (`DdlColType`) layer narrows the advertised OID.
295+
#[test]
296+
fn parse_int_width_aliases_map_to_int64() {
297+
assert_eq!("INT4".parse::<ColumnType>().unwrap(), ColumnType::Int64);
298+
assert_eq!("INT8".parse::<ColumnType>().unwrap(), ColumnType::Int64);
299+
assert_eq!("SMALLINT".parse::<ColumnType>().unwrap(), ColumnType::Int64);
300+
assert_eq!("INT2".parse::<ColumnType>().unwrap(), ColumnType::Int64);
301+
}
302+
289303
#[test]
290304
fn parse_vector() {
291305
assert_eq!(
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
3+
//! Upstream issue #223 regression: `document_strict` and `kv` engines must
4+
//! accept `INT4`/`SMALLINT` (and the other PostgreSQL integer-width
5+
//! keywords) as valid column types. Both engines validate declared column
6+
//! types via `nodedb_types::columnar::ColumnType::from_str`
7+
//! (`nodedb-sql/src/ddl_ast/collection_type.rs`'s `build_strict_schema` /
8+
//! `build_kv_collection_type`), which previously only recognized
9+
//! `BIGINT`/`INT64`/`INTEGER`/`INT` — `INT4`, `INT8`, `SMALLINT`, and `INT2`
10+
//! were rejected with "unknown column type", even though they're valid
11+
//! PostgreSQL integer aliases.
12+
//!
13+
//! This is a DDL-acceptance smoke test only: for these engines the wire OID
14+
//! stays `INT8` (20) regardless of declared width — `raw_type` refinement
15+
//! (issue #217) is schemaless/columnar-only by design, since strict/kv
16+
//! columns are typed authoritatively by the resolved `ColumnType`, not a
17+
//! separately-tracked raw string. See `pgwire_ddl_result_types.rs`'s
18+
//! `create_collection_int_widths_preserve_wire_oids` for the schemaless OID
19+
//! fidelity coverage.
20+
21+
mod common;
22+
23+
use common::pgwire_harness::TestServer;
24+
25+
/// `document_strict` `CREATE COLLECTION` with `INT4`/`SMALLINT` columns must
26+
/// succeed (previously errored with "unknown column type: 'INT4'").
27+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
28+
async fn strict_create_collection_accepts_int_width_aliases() {
29+
let server = TestServer::start().await;
30+
31+
server
32+
.exec(
33+
"CREATE COLLECTION strict_int_widths (\
34+
id TEXT PRIMARY KEY, \
35+
a INT4, \
36+
b SMALLINT, \
37+
c INT2, \
38+
d INT8\
39+
) WITH (engine='document_strict')",
40+
)
41+
.await
42+
.expect("CREATE COLLECTION with INT4/SMALLINT/INT2/INT8 columns must succeed on document_strict");
43+
44+
server
45+
.exec("INSERT INTO strict_int_widths (id, a, b, c, d) VALUES ('r1', 1, 2, 3, 4)")
46+
.await
47+
.expect("INSERT into strict_int_widths must succeed");
48+
49+
let stmt = server
50+
.client
51+
.prepare_typed(
52+
"SELECT id, a, b, c, d FROM strict_int_widths WHERE id = $1",
53+
&[tokio_postgres::types::Type::TEXT],
54+
)
55+
.await
56+
.expect("prepare strict_int_widths select");
57+
let rows = server
58+
.client
59+
.query(&stmt, &[&"r1"])
60+
.await
61+
.expect("execute strict_int_widths select");
62+
assert_eq!(rows.len(), 1, "expected 1 row back from strict_int_widths");
63+
64+
// Load-bearing: document_strict stays INT8 (20) regardless of declared
65+
// width — strict columns carry no `raw_type` (they're already typed
66+
// authoritatively by `ColumnType`), so the issue #217 refinement never
67+
// applies here. Width narrowing is schemaless/columnar-only by design.
68+
for col_name in ["a", "b", "c", "d"] {
69+
if let Some(col) = rows[0].columns().iter().find(|c| c.name() == col_name) {
70+
assert_eq!(
71+
col.type_().oid(),
72+
20,
73+
"strict column '{col_name}' OID must stay INT8 (20), got {}",
74+
col.type_().oid()
75+
);
76+
}
77+
}
78+
}
79+
80+
/// `kv` `CREATE COLLECTION` with `INT4`/`SMALLINT` columns must succeed
81+
/// (previously errored with "unknown column type: 'INT4'"). The kv OID
82+
/// stays INT8 (20) — do not assert a narrowed OID here.
83+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
84+
async fn kv_create_collection_accepts_int_width_aliases() {
85+
let server = TestServer::start().await;
86+
87+
server
88+
.exec(
89+
"CREATE COLLECTION kv_int_widths (\
90+
key TEXT PRIMARY KEY, \
91+
a INT4, \
92+
b SMALLINT\
93+
) WITH (engine='kv')",
94+
)
95+
.await
96+
.expect("CREATE COLLECTION with INT4/SMALLINT columns must succeed on kv");
97+
98+
server
99+
.exec("INSERT INTO kv_int_widths (key, a, b) VALUES ('r1', 1, 2)")
100+
.await
101+
.expect("INSERT into kv_int_widths must succeed");
102+
103+
let stmt = server
104+
.client
105+
.prepare_typed(
106+
"SELECT key, a, b FROM kv_int_widths WHERE key = $1",
107+
&[tokio_postgres::types::Type::TEXT],
108+
)
109+
.await
110+
.expect("prepare kv_int_widths select");
111+
let rows = server
112+
.client
113+
.query(&stmt, &[&"r1"])
114+
.await
115+
.expect("execute kv_int_widths select");
116+
assert_eq!(rows.len(), 1, "expected 1 row back from kv_int_widths");
117+
118+
// Load-bearing: kv stays INT8 (20) — the fix does not widen/narrow kv's
119+
// wire type, only schemaless/columnar's.
120+
for col_name in ["a", "b"] {
121+
if let Some(col) = rows[0].columns().iter().find(|c| c.name() == col_name) {
122+
assert_eq!(
123+
col.type_().oid(),
124+
20,
125+
"kv column '{col_name}' OID must stay INT8 (20), got {}",
126+
col.type_().oid()
127+
);
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)