1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// SPDX-License-Identifier: BUSL-1.1
//! String-recognized versioning DDL arms: version history, maintenance,
//! cluster management, vector-index lifecycle, vector-model metadata, and
//! graph index / tree operations.
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::state::SharedState;
use crate::types::DatabaseId;
use super::super::super::result::{DdlError, DdlResult};
use super::super::cluster;
use super::super::collection;
use super::super::maintenance;
use super::super::tree_ops;
use super::super::version_history;
pub(super) async fn try_string(
state: &SharedState,
identity: &AuthenticatedIdentity,
sql: &str,
upper: &str,
database_id: DatabaseId,
) -> Option<Result<Vec<DdlResult>, DdlError>> {
// Version history. None of `CREATE CHECKPOINT`, `DROP CHECKPOINT`, `SHOW
// VERSIONS OF`, `SELECT … AT VERSION`, `SELECT DIFF(…)`, `RESTORE … SET
// VERSION`, or `COMPACT HISTORY ON` parse into any typed AST variant — the
// pgwire collaborative router dispatched all of them by string prefix from
// the raw SQL. Replicate that exactly here, before the parse gate, so the
// prefix recognition (including the `RESTORE … SET VERSION` guard that keeps
// `RESTORE TENANT` / `RESTORE DATABASE` on the typed path) and syntax
// messages stay byte-identical. Guard ordering mirrors the pgwire router.
if upper.starts_with("CREATE CHECKPOINT ") {
return Some(
version_history::checkpoint::create_checkpoint(state, identity, database_id, sql).await,
);
}
if upper.starts_with("DROP CHECKPOINT ") {
return Some(version_history::checkpoint::drop_checkpoint(
state, identity, sql,
));
}
if upper.starts_with("SHOW VERSIONS OF ") {
return Some(version_history::show_versions::show_versions(
state, identity, sql,
));
}
if upper.contains("AT VERSION") && upper.starts_with("SELECT") {
return Some(
version_history::at_version::select_at_version(state, identity, database_id, sql).await,
);
}
if upper.starts_with("SELECT DIFF(") || upper.starts_with("SELECT DIFF (") {
return Some(version_history::diff::select_diff(state, identity, database_id, sql).await);
}
if upper.starts_with("RESTORE ") && upper.contains("SET VERSION") {
return Some(
version_history::restore::restore_version(state, identity, database_id, sql).await,
);
}
if upper.starts_with("COMPACT HISTORY ON ") {
return Some(
version_history::compact::compact_history(state, identity, database_id, sql).await,
);
}
// Maintenance: ANALYZE / COMPACT / SHOW STORAGE / SHOW COMPACTION STATUS.
// These parse into typed `ClusterStmt` variants, but the pgwire router
// dispatched all four by string prefix from the raw SQL / token slice (the
// pgwire typed-AST path has no arm for them). Replicate that exactly here,
// before the parse gate, so the prefix recognition (trailing space on
// `ANALYZE ` / `COMPACT `, and the `SHOW COMPACTION STATUS` exact / prefix
// forms) and the `parts`-based name extraction stay byte-identical. The
// `COMPACT ` prefix is placed after the version-history `COMPACT HISTORY ON`
// guard above, preserving that `COMPACT HISTORY ON …` routes to
// version_history exactly as the pgwire dispatch (neutral-first) did.
if upper.starts_with("ANALYZE ") {
return Some(maintenance::handle_analyze(state, identity, sql).await);
}
if upper.starts_with("COMPACT ") {
let parts: Vec<&str> = sql.split_whitespace().collect();
return Some(maintenance::handle_compact(state, identity, &parts));
}
if upper.starts_with("SHOW STORAGE ") {
let parts: Vec<&str> = sql.split_whitespace().collect();
return Some(maintenance::handle_show_storage(state, identity, &parts));
}
if upper == "SHOW COMPACTION STATUS" || upper.starts_with("SHOW COMPACTION STATUS ") {
return Some(maintenance::handle_show_compaction_status(state, identity));
}
// Cluster management & observability: SHOW CLUSTER, SHOW RAFT GROUPS,
// SHOW RAFT GROUP <id>, SHOW MIGRATIONS, REBALANCE, SHOW PEER HEALTH,
// SHOW NODES, SHOW NODE <id>, REMOVE NODE <id>, SHOW RANGES, SHOW
// ROUTING, SHOW SCHEMA VERSION. All of these parse into typed
// `ClusterStmt` variants, but the pgwire admin router dispatched them by
// string prefix from the raw SQL / token slice (the pgwire typed-AST path
// only had an arm for `ALTER RAFT GROUP`). Replicate that exactly here,
// before the parse gate, so the prefix recognition (order matters: `SHOW
// RAFT GROUPS` before `SHOW RAFT GROUP `) and the `parts`-based
// extraction stay byte-identical. `ALTER RAFT GROUP` is dispatched via
// the typed match below, exactly as the pgwire router did.
if upper.starts_with("SHOW CLUSTER") {
return Some(cluster::show_cluster(state, identity));
}
if upper.starts_with("SHOW RAFT GROUPS") {
return Some(cluster::show_raft_groups(state, identity));
}
if upper.starts_with("SHOW RAFT GROUP ") {
let parts: Vec<&str> = sql.split_whitespace().collect();
return Some(cluster::show_raft_group(state, identity, &parts));
}
if upper.starts_with("SHOW MIGRATIONS") {
return Some(cluster::show_migrations(state, identity));
}
if upper.starts_with("REBALANCE") {
return Some(cluster::rebalance(state, identity));
}
if upper.starts_with("SHOW PEER HEALTH") {
return Some(cluster::show_peer_health(state, identity));
}
if upper.starts_with("SHOW NODES") {
return Some(cluster::show_nodes(state, identity));
}
if upper.starts_with("SHOW NODE ") {
let parts: Vec<&str> = sql.split_whitespace().collect();
return Some(cluster::show_node(state, identity, &parts));
}
if upper.starts_with("REMOVE NODE ") {
let parts: Vec<&str> = sql.split_whitespace().collect();
return Some(cluster::remove_node(state, identity, &parts));
}
if upper.starts_with("SHOW RANGES") {
return Some(cluster::show_ranges(state, identity));
}
if upper.starts_with("SHOW ROUTING") {
return Some(cluster::show_routing(state, identity));
}
if upper.starts_with("SHOW SCHEMA VERSION") {
return Some(cluster::show_schema_version(state, identity));
}
// Vector index lifecycle: SHOW VECTOR INDEX / ALTER VECTOR INDEX. None of
// these are dispatched from a typed AST arm — the pgwire engine_ops router
// recognized all four by string prefix from the raw SQL. Replicate that
// exactly here, before the parse gate, so the prefix recognition (and the
// ` SEAL` / ` COMPACT` / ` SET ` sub-clause guards, checked in this order)
// stays byte-identical.
if upper.starts_with("SHOW VECTOR INDEX ") {
return Some(maintenance::handle_show_vector_index(state, identity, sql).await);
}
if upper.starts_with("ALTER VECTOR INDEX ") && upper.contains(" SEAL") {
return Some(maintenance::handle_alter_vector_index_seal(state, identity, sql).await);
}
if upper.starts_with("ALTER VECTOR INDEX ") && upper.contains(" COMPACT") {
return Some(maintenance::handle_alter_vector_index_compact(state, identity, sql).await);
}
if upper.starts_with("ALTER VECTOR INDEX ") && upper.contains(" SET ") {
return Some(maintenance::handle_alter_vector_index_set(state, identity, sql).await);
}
// Vector model metadata. None of these are dispatched from a typed AST arm —
// `ALTER COLLECTION ... SET VECTOR METADATA ON` parses into no
// `AlterCollectionOp` variant, and `SHOW VECTOR MODELS` / `SELECT
// VECTOR_METADATA(...)` parse into no typed DDL AST at all. The pgwire
// engine_ops router recognized all three by string prefix from the raw SQL.
// Replicate that exactly here, before the parse gate, so the prefix
// recognition (and the `ALTER COLLECTION ... SET VECTOR METADATA ON` guard
// running before the typed `AlterCollection` parse handling) stays
// byte-identical. The `SET VECTOR METADATA ON` guard precedes the typed
// parse gate below, so it is never shadowed by the migrated typed
// `AlterCollection` dispatch.
if upper.starts_with("ALTER COLLECTION ") && upper.contains("SET VECTOR METADATA ON") {
return Some(collection::handle_set_vector_metadata(
state,
identity,
sql,
database_id,
));
}
if upper.starts_with("SHOW VECTOR MODELS") {
return Some(collection::handle_show_vector_models(state, identity));
}
if upper.starts_with("SELECT VECTOR_METADATA(") || upper.starts_with("SELECT VECTOR_METADATA (")
{
let inner = sql
.find('(')
.and_then(|start| sql.rfind(')').map(|end| &sql[start + 1..end]));
if let Some(args_str) = inner {
let args: Vec<&str> = args_str
.split(',')
.map(|s| s.trim().trim_matches('\'').trim_matches('"'))
.collect();
if args.len() >= 2 && !args[0].is_empty() && !args[1].is_empty() {
return Some(collection::handle_vector_metadata_query(
state,
identity,
&args[0].to_lowercase(),
&args[1].to_lowercase(),
));
}
}
return Some(Err(DdlError {
sqlstate: "42601".to_string(),
message: "usage: SELECT VECTOR_METADATA('collection', 'column')".to_string(),
}));
}
// Graph index and tree operations: CREATE GRAPH INDEX / TREE_SUM /
// TREE_CHILDREN. None of these are dispatched from a typed AST arm — the
// pgwire engine_ops router recognized all three by string prefix from the
// raw SQL (the `SELECT TREE_SUM` / bare `TREE_SUM` and `SELECT
// TREE_CHILDREN` / bare `TREE_CHILDREN` forms never parse into a typed DDL
// AST). Replicate that exactly here, before the parse gate, so the prefix
// recognition and syntax messages stay byte-identical.
if upper.starts_with("CREATE GRAPH INDEX ") {
return Some(tree_ops::create_graph_index(state, identity, database_id, sql).await);
}
if upper.starts_with("SELECT TREE_SUM") || upper.starts_with("TREE_SUM") {
return Some(tree_ops::tree_sum(state, identity, database_id, sql).await);
}
if upper.starts_with("SELECT TREE_CHILDREN") || upper.starts_with("TREE_CHILDREN") {
return Some(tree_ops::tree_children(state, identity, database_id, sql).await);
}
None
}