@lix-js/sdk 0.6.0-preview.2 → 0.6.0-preview.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +46 -8
- package/dist/engine-wasm/wasm/lix_engine.d.ts +25 -1
- package/dist/engine-wasm/wasm/lix_engine.js +60 -2
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +5 -0
- package/dist/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- package/dist/open-lix.d.ts +10 -3
- package/dist/open-lix.js +39 -0
- package/dist-engine-src/src/binary_cas/types.rs +0 -6
- package/dist-engine-src/src/catalog/context.rs +412 -0
- package/dist-engine-src/src/catalog/mod.rs +10 -0
- package/dist-engine-src/src/catalog/schema.rs +4 -0
- package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
- package/dist-engine-src/src/cel/mod.rs +1 -1
- package/dist-engine-src/src/cel/provider.rs +1 -1
- package/dist-engine-src/src/commit_graph/context.rs +328 -1015
- package/dist-engine-src/src/commit_graph/mod.rs +2 -3
- package/dist-engine-src/src/commit_graph/types.rs +7 -43
- package/dist-engine-src/src/commit_graph/walker.rs +57 -81
- package/dist-engine-src/src/commit_store/codec.rs +887 -0
- package/dist-engine-src/src/commit_store/context.rs +944 -0
- package/dist-engine-src/src/commit_store/materialization.rs +84 -0
- package/dist-engine-src/src/commit_store/mod.rs +16 -0
- package/dist-engine-src/src/commit_store/storage.rs +600 -0
- package/dist-engine-src/src/commit_store/types.rs +215 -0
- package/dist-engine-src/src/common/identity.rs +15 -5
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +17 -12
- package/dist-engine-src/src/common/mod.rs +5 -5
- package/dist-engine-src/src/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +29 -43
- package/dist-engine-src/src/entity_identity.rs +238 -118
- package/dist-engine-src/src/functions/context.rs +17 -52
- package/dist-engine-src/src/functions/deterministic.rs +1 -1
- package/dist-engine-src/src/functions/mod.rs +1 -1
- package/dist-engine-src/src/functions/provider.rs +4 -4
- package/dist-engine-src/src/functions/state.rs +39 -66
- package/dist-engine-src/src/functions/types.rs +1 -1
- package/dist-engine-src/src/init.rs +204 -151
- package/dist-engine-src/src/json_store/context.rs +354 -60
- package/dist-engine-src/src/json_store/encoded.rs +6 -6
- package/dist-engine-src/src/json_store/mod.rs +4 -1
- package/dist-engine-src/src/json_store/store.rs +884 -11
- package/dist-engine-src/src/json_store/types.rs +166 -1
- package/dist-engine-src/src/lib.rs +11 -10
- package/dist-engine-src/src/live_state/context.rs +608 -830
- package/dist-engine-src/src/live_state/mod.rs +3 -3
- package/dist-engine-src/src/live_state/overlay.rs +7 -7
- package/dist-engine-src/src/live_state/reader.rs +5 -5
- package/dist-engine-src/src/live_state/types.rs +19 -36
- package/dist-engine-src/src/live_state/visibility.rs +19 -14
- package/dist-engine-src/src/plugin/archive.rs +3 -6
- package/dist-engine-src/src/plugin/install.rs +0 -18
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
- package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +47 -17
- package/dist-engine-src/src/schema/definition.rs +202 -96
- package/dist-engine-src/src/schema/key.rs +9 -77
- package/dist-engine-src/src/schema/mod.rs +4 -4
- package/dist-engine-src/src/schema/tests.rs +133 -92
- package/dist-engine-src/src/session/context.rs +86 -48
- package/dist-engine-src/src/session/create_version.rs +22 -14
- package/dist-engine-src/src/session/execute.rs +117 -23
- package/dist-engine-src/src/session/merge/apply.rs +4 -4
- package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
- package/dist-engine-src/src/session/merge/stats.rs +1 -1
- package/dist-engine-src/src/session/merge/version.rs +35 -45
- package/dist-engine-src/src/session/mod.rs +9 -7
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +17 -28
- package/dist-engine-src/src/session/transaction.rs +76 -0
- package/dist-engine-src/src/sql2/change_provider.rs +14 -20
- package/dist-engine-src/src/sql2/classify.rs +75 -48
- package/dist-engine-src/src/sql2/context.rs +22 -18
- package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
- package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
- package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
- package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
- package/dist-engine-src/src/sql2/error.rs +24 -5
- package/dist-engine-src/src/sql2/execute.rs +426 -272
- package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
- package/dist-engine-src/src/sql2/file_provider.rs +533 -108
- package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
- package/dist-engine-src/src/sql2/history_projection.rs +3 -27
- package/dist-engine-src/src/sql2/history_provider.rs +11 -17
- package/dist-engine-src/src/sql2/history_route.rs +22 -8
- package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
- package/dist-engine-src/src/sql2/mod.rs +8 -4
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
- package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
- package/dist-engine-src/src/sql2/public_bind/dml.rs +172 -0
- package/dist-engine-src/src/sql2/public_bind/mod.rs +26 -0
- package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
- package/dist-engine-src/src/sql2/read_only.rs +10 -12
- package/dist-engine-src/src/sql2/session.rs +7 -10
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
- package/dist-engine-src/src/sql2/udfs/public_call.rs +238 -0
- package/dist-engine-src/src/sql2/version_provider.rs +46 -31
- package/dist-engine-src/src/sql2/version_scope.rs +4 -4
- package/dist-engine-src/src/storage_bench.rs +1782 -325
- package/dist-engine-src/src/test_support.rs +183 -36
- package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
- package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
- package/dist-engine-src/src/tracked_state/context.rs +1155 -271
- package/dist-engine-src/src/tracked_state/diff.rs +249 -57
- package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +37 -19
- package/dist-engine-src/src/tracked_state/mod.rs +8 -7
- package/dist-engine-src/src/tracked_state/storage.rs +138 -6
- package/dist-engine-src/src/tracked_state/tree.rs +695 -252
- package/dist-engine-src/src/tracked_state/types.rs +176 -6
- package/dist-engine-src/src/transaction/commit.rs +695 -435
- package/dist-engine-src/src/transaction/context.rs +551 -310
- package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
- package/dist-engine-src/src/transaction/mod.rs +2 -0
- package/dist-engine-src/src/transaction/normalization.rs +311 -447
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
- package/dist-engine-src/src/transaction/staging.rs +701 -406
- package/dist-engine-src/src/transaction/types.rs +231 -122
- package/dist-engine-src/src/transaction/validation.rs +2717 -1698
- package/dist-engine-src/src/untracked_state/codec.rs +40 -96
- package/dist-engine-src/src/untracked_state/context.rs +21 -5
- package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
- package/dist-engine-src/src/untracked_state/mod.rs +3 -5
- package/dist-engine-src/src/untracked_state/storage.rs +105 -57
- package/dist-engine-src/src/untracked_state/types.rs +63 -13
- package/dist-engine-src/src/version/context.rs +1 -13
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +3 -2
- package/dist-engine-src/src/version/refs.rs +12 -103
- package/dist-engine-src/src/version/stage_rows.rs +15 -19
- package/package.json +1 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -321
- package/dist-engine-src/src/changelog/context.rs +0 -92
- package/dist-engine-src/src/changelog/materialization.rs +0 -121
- package/dist-engine-src/src/changelog/mod.rs +0 -13
- package/dist-engine-src/src/changelog/reader.rs +0 -20
- package/dist-engine-src/src/changelog/storage.rs +0 -220
- package/dist-engine-src/src/changelog/types.rs +0 -38
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
- package/dist-engine-src/src/schema_registry.rs +0 -294
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
- package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
- package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
use datafusion::arrow::datatypes::Field;
|
|
2
2
|
use datafusion::arrow::record_batch::RecordBatch;
|
|
3
|
-
use datafusion::common::
|
|
3
|
+
use datafusion::common::metadata::{FieldMetadata, ScalarAndMetadata};
|
|
4
|
+
use datafusion::common::{ParamValues, ScalarValue};
|
|
4
5
|
use datafusion::logical_expr::{Expr, LogicalPlan, WriteOp};
|
|
5
6
|
use datafusion::prelude::SessionContext;
|
|
7
|
+
use datafusion::sql::parser::{DFParserBuilder, Statement as DataFusionStatement};
|
|
8
|
+
use datafusion::sql::sqlparser::dialect::GenericDialect;
|
|
9
|
+
use datafusion::sql::sqlparser::tokenizer::{Token, Tokenizer};
|
|
6
10
|
use serde_json::{json, Value as JsonValue};
|
|
7
|
-
use std::collections::{BTreeSet, HashSet};
|
|
11
|
+
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
|
8
12
|
|
|
9
13
|
use crate::schema::schema_key_from_definition;
|
|
10
14
|
use crate::{LixError, LixNotice, SqlQueryResult, Value};
|
|
11
15
|
|
|
12
|
-
use super::
|
|
16
|
+
use super::predicate_typecheck::validate_json_predicate_expr_with_dfschema;
|
|
17
|
+
use super::result_metadata::{field_is_json, LIX_VALUE_TYPE_JSON, LIX_VALUE_TYPE_METADATA_KEY};
|
|
13
18
|
use super::session::{build_read_session, build_write_session};
|
|
14
19
|
use super::write_normalization::{
|
|
15
20
|
is_binary_type, lix_file_data_type_lix_error, logical_expr_is_binary_or_null,
|
|
@@ -57,14 +62,26 @@ pub(crate) async fn create_logical_plan(
|
|
|
57
62
|
ctx: &dyn SqlExecutionContext,
|
|
58
63
|
sql: &str,
|
|
59
64
|
) -> Result<SqlLogicalPlan, LixError> {
|
|
60
|
-
|
|
65
|
+
let statement = parse_statement(sql)?;
|
|
66
|
+
create_logical_plan_from_parsed(ctx, sql, statement).await
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub(crate) fn parse_statement(sql: &str) -> Result<DataFusionStatement, LixError> {
|
|
70
|
+
parse_datafusion_statement(sql)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub(crate) async fn create_logical_plan_from_parsed(
|
|
74
|
+
ctx: &dyn SqlExecutionContext,
|
|
75
|
+
sql: &str,
|
|
76
|
+
statement: DataFusionStatement,
|
|
77
|
+
) -> Result<SqlLogicalPlan, LixError> {
|
|
78
|
+
validate_public_read_sql_surface(sql)?;
|
|
79
|
+
super::validate_supported_datafusion_statement_ast(&statement)?;
|
|
80
|
+
super::udfs::validate_public_udf_calls_in_datafusion_statement(&statement)?;
|
|
61
81
|
let session = build_read_session(ctx).await?;
|
|
62
|
-
let plan = session
|
|
63
|
-
.state()
|
|
64
|
-
.create_logical_plan(sql)
|
|
65
|
-
.await
|
|
66
|
-
.map_err(datafusion_error_to_lix_error)?;
|
|
82
|
+
let plan = create_logical_plan_from_statement(&session, statement).await?;
|
|
67
83
|
validate_supported_logical_plan(&plan)?;
|
|
84
|
+
validate_json_predicates_in_logical_plan(&plan)?;
|
|
68
85
|
let kind = classify_logical_plan(&plan);
|
|
69
86
|
let notices = history_filter_notices(&plan);
|
|
70
87
|
|
|
@@ -82,15 +99,24 @@ pub(crate) async fn create_write_logical_plan(
|
|
|
82
99
|
ctx: &mut dyn SqlWriteExecutionContext,
|
|
83
100
|
sql: &str,
|
|
84
101
|
) -> Result<SqlLogicalPlan, LixError> {
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
let statement = parse_statement(sql)?;
|
|
103
|
+
create_write_logical_plan_from_parsed(ctx, statement).await
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub(crate) async fn create_write_logical_plan_from_parsed(
|
|
107
|
+
ctx: &mut dyn SqlWriteExecutionContext,
|
|
108
|
+
statement: DataFusionStatement,
|
|
109
|
+
) -> Result<SqlLogicalPlan, LixError> {
|
|
110
|
+
super::udfs::validate_public_udf_calls_in_datafusion_statement(&statement)?;
|
|
111
|
+
let visible_schemas = ctx.list_visible_schemas()?;
|
|
112
|
+
super::public_bind::validate_public_dml_statement(&statement, &visible_schemas)?;
|
|
113
|
+
super::validate_supported_datafusion_statement_ast(&statement)?;
|
|
114
|
+
reject_read_only_history_view_dml_from_statement(&statement, &visible_schemas)?;
|
|
87
115
|
let session = build_write_session(ctx).await?;
|
|
88
|
-
let plan = session
|
|
89
|
-
.state()
|
|
90
|
-
.create_logical_plan(sql)
|
|
91
|
-
.await
|
|
92
|
-
.map_err(datafusion_error_to_lix_error)?;
|
|
116
|
+
let plan = create_logical_plan_from_statement(&session, statement).await?;
|
|
93
117
|
validate_supported_logical_plan(&plan)?;
|
|
118
|
+
super::public_bind::validate_public_dml_plan(&plan, &visible_schemas)?;
|
|
119
|
+
validate_json_predicates_in_logical_plan(&plan)?;
|
|
94
120
|
let strict_binary_params = validate_strict_lix_file_data_writes(&plan)?;
|
|
95
121
|
let kind = classify_logical_plan(&plan);
|
|
96
122
|
|
|
@@ -103,6 +129,141 @@ pub(crate) async fn create_write_logical_plan(
|
|
|
103
129
|
})
|
|
104
130
|
}
|
|
105
131
|
|
|
132
|
+
pub(crate) async fn create_transaction_read_logical_plan_from_parsed(
|
|
133
|
+
ctx: &mut dyn SqlWriteExecutionContext,
|
|
134
|
+
sql: &str,
|
|
135
|
+
statement: DataFusionStatement,
|
|
136
|
+
) -> Result<SqlLogicalPlan, LixError> {
|
|
137
|
+
validate_public_read_sql_surface(sql)?;
|
|
138
|
+
super::validate_supported_datafusion_statement_ast(&statement)?;
|
|
139
|
+
super::udfs::validate_public_udf_calls_in_datafusion_statement(&statement)?;
|
|
140
|
+
let session = build_write_session(ctx).await?;
|
|
141
|
+
let plan = create_logical_plan_from_statement(&session, statement).await?;
|
|
142
|
+
validate_supported_logical_plan(&plan)?;
|
|
143
|
+
validate_json_predicates_in_logical_plan(&plan)?;
|
|
144
|
+
let kind = classify_logical_plan(&plan);
|
|
145
|
+
let notices = history_filter_notices(&plan);
|
|
146
|
+
|
|
147
|
+
Ok(SqlLogicalPlan {
|
|
148
|
+
session,
|
|
149
|
+
plan,
|
|
150
|
+
kind,
|
|
151
|
+
notices,
|
|
152
|
+
strict_binary_params: BTreeSet::new(),
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn validate_public_read_sql_surface(sql: &str) -> Result<(), LixError> {
|
|
157
|
+
let normalized = sql.to_ascii_lowercase();
|
|
158
|
+
if normalized.contains("lower(path)") {
|
|
159
|
+
return Err(LixError::new(
|
|
160
|
+
LixError::CODE_UNSUPPORTED_SQL,
|
|
161
|
+
"public column 'path' must be compared directly to a literal or parameter",
|
|
162
|
+
));
|
|
163
|
+
}
|
|
164
|
+
if normalized.contains("lixcol_version_id")
|
|
165
|
+
&& (normalized.contains("= lower(") || normalized.contains(" in (lower("))
|
|
166
|
+
{
|
|
167
|
+
return Err(LixError::new(
|
|
168
|
+
LixError::CODE_UNSUPPORTED_SQL,
|
|
169
|
+
"public column 'lixcol_version_id' must be compared directly to a literal or parameter",
|
|
170
|
+
));
|
|
171
|
+
}
|
|
172
|
+
Ok(())
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn parse_datafusion_statement(sql: &str) -> Result<DataFusionStatement, LixError> {
|
|
176
|
+
let dialect = GenericDialect {};
|
|
177
|
+
let mut next_index = 1usize;
|
|
178
|
+
let mut has_anonymous = false;
|
|
179
|
+
let mut explicit_placeholders = Vec::new();
|
|
180
|
+
|
|
181
|
+
let mut tokens = Vec::new();
|
|
182
|
+
Tokenizer::new(&dialect, sql)
|
|
183
|
+
.tokenize_with_location_into_buf_with_mapper(&mut tokens, |mut token_span| {
|
|
184
|
+
if let Token::Placeholder(placeholder) = &token_span.token {
|
|
185
|
+
if placeholder == "?" {
|
|
186
|
+
has_anonymous = true;
|
|
187
|
+
token_span.token = Token::Placeholder(format!("${next_index}"));
|
|
188
|
+
next_index += 1;
|
|
189
|
+
} else {
|
|
190
|
+
explicit_placeholders.push(placeholder.clone());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
token_span
|
|
194
|
+
})
|
|
195
|
+
.map_err(|error| {
|
|
196
|
+
LixError::new(
|
|
197
|
+
LixError::CODE_PARSE_ERROR,
|
|
198
|
+
format!("sql2 SQL tokenize error: {error}"),
|
|
199
|
+
)
|
|
200
|
+
})?;
|
|
201
|
+
|
|
202
|
+
if has_anonymous && !explicit_placeholders.is_empty() {
|
|
203
|
+
return Err(LixError::new(
|
|
204
|
+
LixError::CODE_PARSE_ERROR,
|
|
205
|
+
"SQL mixes anonymous and explicit parameter placeholders",
|
|
206
|
+
)
|
|
207
|
+
.with_hint("Use either anonymous placeholders like ?, ? or numbered placeholders like $1, $2, but not both.")
|
|
208
|
+
.with_details(json!({
|
|
209
|
+
"operation": "execute",
|
|
210
|
+
"explicit_placeholders": explicit_placeholders,
|
|
211
|
+
})));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let mut statements = DFParserBuilder::new(tokens)
|
|
215
|
+
.with_dialect(&dialect)
|
|
216
|
+
.build()
|
|
217
|
+
.map_err(datafusion_error_to_lix_error)?
|
|
218
|
+
.parse_statements()
|
|
219
|
+
.map_err(datafusion_error_to_lix_error)?;
|
|
220
|
+
|
|
221
|
+
if statements.len() > 1 {
|
|
222
|
+
return Err(LixError::new(
|
|
223
|
+
LixError::CODE_UNSUPPORTED_SQL,
|
|
224
|
+
"Lix SQL only supports one statement per execute() call",
|
|
225
|
+
));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
statements.pop_front().ok_or_else(|| {
|
|
229
|
+
LixError::new(
|
|
230
|
+
LixError::CODE_PARSE_ERROR,
|
|
231
|
+
"sql2 DataFusion error: No SQL statements were provided in the query string",
|
|
232
|
+
)
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async fn create_logical_plan_from_statement(
|
|
237
|
+
session: &SessionContext,
|
|
238
|
+
statement: DataFusionStatement,
|
|
239
|
+
) -> Result<LogicalPlan, LixError> {
|
|
240
|
+
session
|
|
241
|
+
.state()
|
|
242
|
+
.statement_to_plan(statement)
|
|
243
|
+
.await
|
|
244
|
+
.map_err(datafusion_error_to_lix_error)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fn validate_json_predicates_in_logical_plan(plan: &LogicalPlan) -> Result<(), LixError> {
|
|
248
|
+
match plan {
|
|
249
|
+
LogicalPlan::Filter(filter) => {
|
|
250
|
+
validate_json_predicate_expr_with_dfschema(filter.input.schema(), &filter.predicate)?;
|
|
251
|
+
}
|
|
252
|
+
LogicalPlan::TableScan(scan) => {
|
|
253
|
+
for filter in &scan.filters {
|
|
254
|
+
validate_json_predicate_expr_with_dfschema(scan.projected_schema.as_ref(), filter)?;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
_ => {}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for input in plan.inputs() {
|
|
261
|
+
validate_json_predicates_in_logical_plan(input)?;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Ok(())
|
|
265
|
+
}
|
|
266
|
+
|
|
106
267
|
fn validate_strict_lix_file_data_writes(plan: &LogicalPlan) -> Result<BTreeSet<usize>, LixError> {
|
|
107
268
|
let mut strict_binary_params = BTreeSet::new();
|
|
108
269
|
let LogicalPlan::Dml(dml) = plan else {
|
|
@@ -200,7 +361,7 @@ fn placeholder_index(id: &str) -> Result<usize, LixError> {
|
|
|
200
361
|
LixError::CODE_PARSE_ERROR,
|
|
201
362
|
format!("unsupported SQL parameter placeholder '{id}'"),
|
|
202
363
|
)
|
|
203
|
-
.with_hint("Use numbered placeholders like $1, $2, ...")
|
|
364
|
+
.with_hint("Use placeholders like ?, ? or numbered placeholders like $1, $2, ...")
|
|
204
365
|
})
|
|
205
366
|
}
|
|
206
367
|
|
|
@@ -224,12 +385,9 @@ pub(crate) async fn execute_logical_plan(
|
|
|
224
385
|
.map_err(datafusion_error_to_lix_error)?;
|
|
225
386
|
if !params.is_empty() {
|
|
226
387
|
dataframe = dataframe
|
|
227
|
-
.with_param_values(
|
|
228
|
-
params
|
|
229
|
-
|
|
230
|
-
.map(scalar_value_from_lix_value)
|
|
231
|
-
.collect::<Vec<_>>(),
|
|
232
|
-
)
|
|
388
|
+
.with_param_values(ParamValues::List(
|
|
389
|
+
params.iter().map(scalar_value_from_lix_value).collect(),
|
|
390
|
+
))
|
|
233
391
|
.map_err(datafusion_error_to_lix_error)?;
|
|
234
392
|
}
|
|
235
393
|
|
|
@@ -298,7 +456,7 @@ fn expected_positional_parameter_count(
|
|
|
298
456
|
LixError::CODE_PARSE_ERROR,
|
|
299
457
|
format!("unsupported SQL parameter placeholder '{name}'"),
|
|
300
458
|
)
|
|
301
|
-
.with_hint("Use numbered placeholders like $1, $2, ...")
|
|
459
|
+
.with_hint("Use placeholders like ?, ? or numbered placeholders like $1, $2, ...")
|
|
302
460
|
.with_details(json!({
|
|
303
461
|
"operation": "execute",
|
|
304
462
|
"placeholder": name,
|
|
@@ -309,7 +467,7 @@ fn expected_positional_parameter_count(
|
|
|
309
467
|
LixError::CODE_PARSE_ERROR,
|
|
310
468
|
"SQL parameter placeholders are 1-indexed",
|
|
311
469
|
)
|
|
312
|
-
.with_hint("Use numbered placeholders like $1, $2, ...")
|
|
470
|
+
.with_hint("Use placeholders like ?, ? or numbered placeholders like $1, $2, ...")
|
|
313
471
|
.with_details(json!({
|
|
314
472
|
"operation": "execute",
|
|
315
473
|
"placeholder": name,
|
|
@@ -326,11 +484,11 @@ fn sorted_parameter_names(parameter_names: &HashSet<String>) -> Vec<String> {
|
|
|
326
484
|
names
|
|
327
485
|
}
|
|
328
486
|
|
|
329
|
-
fn
|
|
330
|
-
|
|
487
|
+
fn reject_read_only_history_view_dml_from_statement(
|
|
488
|
+
statement: &DataFusionStatement,
|
|
331
489
|
visible_schemas: &[JsonValue],
|
|
332
490
|
) -> Result<(), LixError> {
|
|
333
|
-
let target_names = super::
|
|
491
|
+
let target_names = super::datafusion_statement_dml_target_table_names(statement);
|
|
334
492
|
for target_name in target_names {
|
|
335
493
|
if is_history_view_name(&target_name, visible_schemas)? {
|
|
336
494
|
return Err(read_only_history_view_error(&target_name));
|
|
@@ -419,18 +577,28 @@ fn validate_supported_logical_plan(plan: &LogicalPlan) -> Result<(), LixError> {
|
|
|
419
577
|
Ok(())
|
|
420
578
|
}
|
|
421
579
|
|
|
422
|
-
fn scalar_value_from_lix_value(value: &Value) ->
|
|
580
|
+
fn scalar_value_from_lix_value(value: &Value) -> ScalarAndMetadata {
|
|
423
581
|
match value {
|
|
424
|
-
Value::Null => ScalarValue::Null,
|
|
425
|
-
Value::Boolean(value) => ScalarValue::Boolean(Some(*value)),
|
|
426
|
-
Value::Integer(value) => ScalarValue::Int64(Some(*value)),
|
|
427
|
-
Value::Real(value) => ScalarValue::Float64(Some(*value)),
|
|
428
|
-
Value::Text(value) => ScalarValue::Utf8(Some(value.clone())),
|
|
429
|
-
Value::Json(value) =>
|
|
430
|
-
|
|
582
|
+
Value::Null => ScalarValue::Null.into(),
|
|
583
|
+
Value::Boolean(value) => ScalarValue::Boolean(Some(*value)).into(),
|
|
584
|
+
Value::Integer(value) => ScalarValue::Int64(Some(*value)).into(),
|
|
585
|
+
Value::Real(value) => ScalarValue::Float64(Some(*value)).into(),
|
|
586
|
+
Value::Text(value) => ScalarValue::Utf8(Some(value.clone())).into(),
|
|
587
|
+
Value::Json(value) => ScalarAndMetadata::new(
|
|
588
|
+
ScalarValue::Utf8(Some(value.to_string())),
|
|
589
|
+
Some(json_field_metadata()),
|
|
590
|
+
),
|
|
591
|
+
Value::Blob(value) => ScalarValue::Binary(Some(value.clone())).into(),
|
|
431
592
|
}
|
|
432
593
|
}
|
|
433
594
|
|
|
595
|
+
fn json_field_metadata() -> FieldMetadata {
|
|
596
|
+
FieldMetadata::new(BTreeMap::from([(
|
|
597
|
+
LIX_VALUE_TYPE_METADATA_KEY.to_string(),
|
|
598
|
+
LIX_VALUE_TYPE_JSON.to_string(),
|
|
599
|
+
)]))
|
|
600
|
+
}
|
|
601
|
+
|
|
434
602
|
fn datafusion_error_to_lix_error(error: datafusion::error::DataFusionError) -> LixError {
|
|
435
603
|
super::error::datafusion_error_to_lix_error(error)
|
|
436
604
|
}
|
|
@@ -660,27 +828,30 @@ mod tests {
|
|
|
660
828
|
SqlWriteExecutionContext,
|
|
661
829
|
};
|
|
662
830
|
use crate::binary_cas::BlobDataReader;
|
|
663
|
-
use crate::changelog::{CanonicalChange, ChangelogReader, ChangelogScanRequest};
|
|
664
831
|
use crate::commit_graph::{
|
|
665
|
-
CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest,
|
|
666
|
-
|
|
667
|
-
ReachableCommitGraphCommit,
|
|
832
|
+
CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest, CommitGraphCommit,
|
|
833
|
+
CommitGraphEdge, CommitGraphReader, ReachableCommitGraphCommit,
|
|
668
834
|
};
|
|
835
|
+
use crate::commit_store::CommitStoreContext;
|
|
669
836
|
use crate::functions::{
|
|
670
837
|
FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
|
|
671
838
|
};
|
|
672
839
|
use crate::json_store::JsonStoreContext;
|
|
673
840
|
use crate::live_state::{
|
|
674
|
-
LiveStateContext, LiveStateReader,
|
|
841
|
+
LiveStateContext, LiveStateReader, LiveStateRowRequest, LiveStateScanRequest,
|
|
842
|
+
MaterializedLiveStateRow,
|
|
675
843
|
};
|
|
676
|
-
use crate::sql2::{
|
|
844
|
+
use crate::sql2::{CommitStoreQuerySource, SqlCommitStoreQuerySource};
|
|
677
845
|
use crate::storage::{
|
|
678
846
|
KvEntryPage, KvExistsBatch, KvGetRequest, KvKeyPage, KvScanRequest, KvValueBatch,
|
|
679
847
|
KvValuePage, StorageContext, StorageReadScope, StorageReadTransaction, StorageReader,
|
|
680
848
|
StorageWriteSet,
|
|
681
849
|
};
|
|
682
850
|
use crate::tracked_state::TrackedStateContext;
|
|
683
|
-
use crate::transaction::
|
|
851
|
+
use crate::transaction::prepare_version_ref_row;
|
|
852
|
+
use crate::transaction::types::{
|
|
853
|
+
TransactionWrite, TransactionWriteOutcome, TransactionWriteRow,
|
|
854
|
+
};
|
|
684
855
|
use crate::untracked_state::UntrackedStateContext;
|
|
685
856
|
use crate::version::VersionRefReader;
|
|
686
857
|
use crate::{Engine, ExecuteResult, SessionContext};
|
|
@@ -689,10 +860,9 @@ mod tests {
|
|
|
689
860
|
struct DummyBlobReader;
|
|
690
861
|
struct DummyLiveStateReader;
|
|
691
862
|
struct RowsLiveStateReader {
|
|
692
|
-
rows: Vec<
|
|
863
|
+
rows: Vec<MaterializedLiveStateRow>,
|
|
693
864
|
}
|
|
694
865
|
struct BackendBlobReader(StorageContext);
|
|
695
|
-
struct DummyChangelogReader;
|
|
696
866
|
struct DummyCommitGraphReader;
|
|
697
867
|
struct DummyVersionRefReader;
|
|
698
868
|
struct TestReadTransaction(StorageContext);
|
|
@@ -747,7 +917,7 @@ mod tests {
|
|
|
747
917
|
|
|
748
918
|
#[derive(Clone)]
|
|
749
919
|
struct CapturedStageWrite {
|
|
750
|
-
rows: Vec<
|
|
920
|
+
rows: Vec<TransactionWriteRow>,
|
|
751
921
|
}
|
|
752
922
|
|
|
753
923
|
impl CapturedStageWrite {
|
|
@@ -759,7 +929,7 @@ mod tests {
|
|
|
759
929
|
}
|
|
760
930
|
|
|
761
931
|
struct CapturedStageOverlay {
|
|
762
|
-
rows: Vec<
|
|
932
|
+
rows: Vec<TransactionWriteRow>,
|
|
763
933
|
}
|
|
764
934
|
|
|
765
935
|
impl CapturedStageOverlay {
|
|
@@ -787,33 +957,31 @@ mod tests {
|
|
|
787
957
|
struct CapturedStageRow {
|
|
788
958
|
entity_id: String,
|
|
789
959
|
schema_key: String,
|
|
790
|
-
schema_version: String,
|
|
791
960
|
version_id: String,
|
|
792
961
|
file_id: Option<String>,
|
|
793
962
|
snapshot_content: Option<String>,
|
|
794
|
-
metadata: Option<
|
|
963
|
+
metadata: Option<String>,
|
|
795
964
|
global: bool,
|
|
796
965
|
untracked: bool,
|
|
797
966
|
tombstone: bool,
|
|
798
967
|
}
|
|
799
968
|
|
|
800
|
-
impl From<
|
|
801
|
-
fn from(row:
|
|
969
|
+
impl From<TransactionWriteRow> for CapturedStageRow {
|
|
970
|
+
fn from(row: TransactionWriteRow) -> Self {
|
|
802
971
|
Self {
|
|
803
972
|
entity_id: row
|
|
804
973
|
.entity_id
|
|
805
974
|
.expect("captured staged row should carry entity_id")
|
|
806
|
-
.
|
|
975
|
+
.as_json_array_text()
|
|
807
976
|
.expect("captured staged row should project entity_id"),
|
|
808
977
|
schema_key: row.schema_key,
|
|
809
|
-
schema_version: row.schema_version,
|
|
810
978
|
version_id: row.version_id,
|
|
811
979
|
file_id: row.file_id,
|
|
812
980
|
global: row.global,
|
|
813
981
|
untracked: row.untracked,
|
|
814
|
-
tombstone: row.
|
|
815
|
-
snapshot_content: row.
|
|
816
|
-
metadata: row.metadata,
|
|
982
|
+
tombstone: row.snapshot.is_none(),
|
|
983
|
+
snapshot_content: row.snapshot.map(|snapshot| snapshot.to_string()),
|
|
984
|
+
metadata: row.metadata.map(|metadata| metadata.to_string()),
|
|
817
985
|
}
|
|
818
986
|
}
|
|
819
987
|
}
|
|
@@ -842,13 +1010,13 @@ mod tests {
|
|
|
842
1010
|
Arc::clone(&self.blob_reader)
|
|
843
1011
|
}
|
|
844
1012
|
|
|
845
|
-
fn
|
|
1013
|
+
fn commit_store_query_source(&self) -> SqlCommitStoreQuerySource {
|
|
846
1014
|
let base_scope = test_read_scope(StorageContext::new(Arc::new(
|
|
847
1015
|
crate::backend::testing::UnitTestBackend::new(),
|
|
848
1016
|
)));
|
|
849
1017
|
let read_scope = StorageReadScope::new(base_scope.store());
|
|
850
|
-
|
|
851
|
-
|
|
1018
|
+
CommitStoreQuerySource {
|
|
1019
|
+
commit_store_reader: Arc::new(CommitStoreContext::new().reader(read_scope.store())),
|
|
852
1020
|
json_reader: JsonStoreContext::new().reader(read_scope.store()),
|
|
853
1021
|
}
|
|
854
1022
|
}
|
|
@@ -898,7 +1066,7 @@ mod tests {
|
|
|
898
1066
|
async fn scan_live_state(
|
|
899
1067
|
&mut self,
|
|
900
1068
|
request: &LiveStateScanRequest,
|
|
901
|
-
) -> Result<Vec<
|
|
1069
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
902
1070
|
self.live_state.scan_rows(request).await
|
|
903
1071
|
}
|
|
904
1072
|
|
|
@@ -909,23 +1077,26 @@ mod tests {
|
|
|
909
1077
|
Ok(Some(format!("commit-{version_id}")))
|
|
910
1078
|
}
|
|
911
1079
|
|
|
912
|
-
async fn stage_write(
|
|
1080
|
+
async fn stage_write(
|
|
1081
|
+
&mut self,
|
|
1082
|
+
write: TransactionWrite,
|
|
1083
|
+
) -> Result<TransactionWriteOutcome, LixError> {
|
|
913
1084
|
let count = match &write {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1085
|
+
TransactionWrite::Rows { rows, .. } => rows.len() as u64,
|
|
1086
|
+
TransactionWrite::RowsWithFileData { count, .. } => *count,
|
|
1087
|
+
TransactionWrite::AdoptedChanges { changes } => changes.len() as u64,
|
|
917
1088
|
};
|
|
918
1089
|
let rows = match write {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1090
|
+
TransactionWrite::Rows { rows, .. } => rows,
|
|
1091
|
+
TransactionWrite::RowsWithFileData { rows, .. } => rows,
|
|
1092
|
+
TransactionWrite::AdoptedChanges { .. } => Vec::new(),
|
|
922
1093
|
};
|
|
923
1094
|
self.staged_writes
|
|
924
1095
|
.lock()
|
|
925
1096
|
.expect("staged writes lock")
|
|
926
1097
|
.deltas
|
|
927
1098
|
.push(CapturedStageWrite { rows });
|
|
928
|
-
Ok(
|
|
1099
|
+
Ok(TransactionWriteOutcome { count })
|
|
929
1100
|
}
|
|
930
1101
|
}
|
|
931
1102
|
|
|
@@ -938,20 +1109,6 @@ mod tests {
|
|
|
938
1109
|
execute_logical_plan(plan, params).await
|
|
939
1110
|
}
|
|
940
1111
|
|
|
941
|
-
#[async_trait]
|
|
942
|
-
impl ChangelogReader for DummyChangelogReader {
|
|
943
|
-
async fn load_change(&self, _change_id: &str) -> Result<Option<CanonicalChange>, LixError> {
|
|
944
|
-
Ok(None)
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
async fn scan_changes(
|
|
948
|
-
&self,
|
|
949
|
-
_request: &ChangelogScanRequest,
|
|
950
|
-
) -> Result<Vec<CanonicalChange>, LixError> {
|
|
951
|
-
Ok(Vec::new())
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
1112
|
#[async_trait]
|
|
956
1113
|
impl VersionRefReader for DummyVersionRefReader {
|
|
957
1114
|
async fn load_head(
|
|
@@ -1009,17 +1166,6 @@ mod tests {
|
|
|
1009
1166
|
Vec::new()
|
|
1010
1167
|
}
|
|
1011
1168
|
|
|
1012
|
-
fn change_sets(&self, _commits: &[CommitGraphCommit]) -> Vec<CommitGraphChangeSet> {
|
|
1013
|
-
Vec::new()
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
async fn change_set_elements(
|
|
1017
|
-
&mut self,
|
|
1018
|
-
_commits: &[CommitGraphCommit],
|
|
1019
|
-
) -> Result<Vec<CommitGraphChangeSetElement>, LixError> {
|
|
1020
|
-
Ok(Vec::new())
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
1169
|
async fn change_history_from_commit(
|
|
1024
1170
|
&mut self,
|
|
1025
1171
|
_start_commit_id: &str,
|
|
@@ -1034,14 +1180,14 @@ mod tests {
|
|
|
1034
1180
|
async fn scan_rows(
|
|
1035
1181
|
&self,
|
|
1036
1182
|
_request: &LiveStateScanRequest,
|
|
1037
|
-
) -> Result<Vec<
|
|
1183
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
1038
1184
|
Ok(vec![])
|
|
1039
1185
|
}
|
|
1040
1186
|
|
|
1041
1187
|
async fn load_row(
|
|
1042
1188
|
&self,
|
|
1043
1189
|
_request: &LiveStateRowRequest,
|
|
1044
|
-
) -> Result<Option<
|
|
1190
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1045
1191
|
Ok(None)
|
|
1046
1192
|
}
|
|
1047
1193
|
}
|
|
@@ -1051,14 +1197,14 @@ mod tests {
|
|
|
1051
1197
|
async fn scan_rows(
|
|
1052
1198
|
&self,
|
|
1053
1199
|
_request: &LiveStateScanRequest,
|
|
1054
|
-
) -> Result<Vec<
|
|
1200
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
1055
1201
|
Ok(self.rows.clone())
|
|
1056
1202
|
}
|
|
1057
1203
|
|
|
1058
1204
|
async fn load_row(
|
|
1059
1205
|
&self,
|
|
1060
1206
|
_request: &LiveStateRowRequest,
|
|
1061
|
-
) -> Result<Option<
|
|
1207
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1062
1208
|
Ok(None)
|
|
1063
1209
|
}
|
|
1064
1210
|
}
|
|
@@ -1069,7 +1215,10 @@ mod tests {
|
|
|
1069
1215
|
&self,
|
|
1070
1216
|
hashes: &[crate::binary_cas::BlobHash],
|
|
1071
1217
|
) -> Result<crate::binary_cas::BlobBytesBatch, LixError> {
|
|
1072
|
-
Ok(crate::binary_cas::BlobBytesBatch::
|
|
1218
|
+
Ok(crate::binary_cas::BlobBytesBatch::new(vec![
|
|
1219
|
+
None;
|
|
1220
|
+
hashes.len()
|
|
1221
|
+
]))
|
|
1073
1222
|
}
|
|
1074
1223
|
}
|
|
1075
1224
|
|
|
@@ -1085,17 +1234,14 @@ mod tests {
|
|
|
1085
1234
|
}
|
|
1086
1235
|
}
|
|
1087
1236
|
|
|
1088
|
-
fn live_lix_state_row(entity_id: &str, metadata: Option<&str>) ->
|
|
1089
|
-
|
|
1090
|
-
entity_id: crate::entity_identity::EntityIdentity::
|
|
1091
|
-
.expect("entity id should decode"),
|
|
1237
|
+
fn live_lix_state_row(entity_id: &str, metadata: Option<&str>) -> MaterializedLiveStateRow {
|
|
1238
|
+
MaterializedLiveStateRow {
|
|
1239
|
+
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
1092
1240
|
schema_key: "lix_key_value".to_string(),
|
|
1093
1241
|
file_id: None,
|
|
1094
1242
|
snapshot_content: Some("{\"key\":\"hello\",\"value\":\"world\"}".to_string()),
|
|
1095
|
-
metadata: metadata.map(
|
|
1096
|
-
|
|
1097
|
-
}),
|
|
1098
|
-
schema_version: "1".to_string(),
|
|
1243
|
+
metadata: metadata.map(str::to_string),
|
|
1244
|
+
deleted: false,
|
|
1099
1245
|
version_id: "version-a".to_string(),
|
|
1100
1246
|
change_id: Some(format!("change-{entity_id}")),
|
|
1101
1247
|
commit_id: Some(format!("commit-{entity_id}")),
|
|
@@ -1106,15 +1252,14 @@ mod tests {
|
|
|
1106
1252
|
}
|
|
1107
1253
|
}
|
|
1108
1254
|
|
|
1109
|
-
fn live_entity_row(entity_id: &str, version_id: &str, value: &str) ->
|
|
1110
|
-
|
|
1111
|
-
entity_id: crate::entity_identity::EntityIdentity::
|
|
1112
|
-
.expect("entity id should decode"),
|
|
1255
|
+
fn live_entity_row(entity_id: &str, version_id: &str, value: &str) -> MaterializedLiveStateRow {
|
|
1256
|
+
MaterializedLiveStateRow {
|
|
1257
|
+
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
1113
1258
|
schema_key: "test_state_schema".to_string(),
|
|
1114
1259
|
file_id: None,
|
|
1115
1260
|
snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
|
|
1116
|
-
metadata: Some(json!({ "source": entity_id })),
|
|
1117
|
-
|
|
1261
|
+
metadata: Some(json!({ "source": entity_id }).to_string()),
|
|
1262
|
+
deleted: false,
|
|
1118
1263
|
version_id: version_id.to_string(),
|
|
1119
1264
|
change_id: Some(format!("change-{entity_id}")),
|
|
1120
1265
|
commit_id: Some(format!("commit-{entity_id}")),
|
|
@@ -1131,10 +1276,9 @@ mod tests {
|
|
|
1131
1276
|
parent_id: Option<&str>,
|
|
1132
1277
|
name: &str,
|
|
1133
1278
|
hidden: bool,
|
|
1134
|
-
) ->
|
|
1135
|
-
|
|
1136
|
-
entity_id: crate::entity_identity::EntityIdentity::
|
|
1137
|
-
.expect("entity id should decode"),
|
|
1279
|
+
) -> MaterializedLiveStateRow {
|
|
1280
|
+
MaterializedLiveStateRow {
|
|
1281
|
+
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
1138
1282
|
schema_key: "lix_directory_descriptor".to_string(),
|
|
1139
1283
|
file_id: None,
|
|
1140
1284
|
snapshot_content: Some(
|
|
@@ -1146,8 +1290,8 @@ mod tests {
|
|
|
1146
1290
|
})
|
|
1147
1291
|
.to_string(),
|
|
1148
1292
|
),
|
|
1149
|
-
metadata: Some(json!({ "source": entity_id })),
|
|
1150
|
-
|
|
1293
|
+
metadata: Some(json!({ "source": entity_id }).to_string()),
|
|
1294
|
+
deleted: false,
|
|
1151
1295
|
version_id: version_id.to_string(),
|
|
1152
1296
|
change_id: Some(format!("change-{entity_id}")),
|
|
1153
1297
|
commit_id: Some(format!("commit-{entity_id}")),
|
|
@@ -1164,10 +1308,9 @@ mod tests {
|
|
|
1164
1308
|
directory_id: Option<&str>,
|
|
1165
1309
|
name: &str,
|
|
1166
1310
|
hidden: bool,
|
|
1167
|
-
) ->
|
|
1168
|
-
|
|
1169
|
-
entity_id: crate::entity_identity::EntityIdentity::
|
|
1170
|
-
.expect("entity id should decode"),
|
|
1311
|
+
) -> MaterializedLiveStateRow {
|
|
1312
|
+
MaterializedLiveStateRow {
|
|
1313
|
+
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
1171
1314
|
schema_key: "lix_file_descriptor".to_string(),
|
|
1172
1315
|
file_id: None,
|
|
1173
1316
|
snapshot_content: Some(
|
|
@@ -1179,8 +1322,8 @@ mod tests {
|
|
|
1179
1322
|
})
|
|
1180
1323
|
.to_string(),
|
|
1181
1324
|
),
|
|
1182
|
-
metadata: Some(json!({ "source": entity_id })),
|
|
1183
|
-
|
|
1325
|
+
metadata: Some(json!({ "source": entity_id }).to_string()),
|
|
1326
|
+
deleted: false,
|
|
1184
1327
|
version_id: version_id.to_string(),
|
|
1185
1328
|
change_id: Some(format!("change-{entity_id}")),
|
|
1186
1329
|
commit_id: Some(format!("commit-{entity_id}")),
|
|
@@ -1317,7 +1460,7 @@ mod tests {
|
|
|
1317
1460
|
}));
|
|
1318
1461
|
}
|
|
1319
1462
|
|
|
1320
|
-
async fn
|
|
1463
|
+
async fn setup_engine_history_fixture() -> Result<(SessionContext, String), LixError> {
|
|
1321
1464
|
let backend = crate::backend::testing::UnitTestBackend::new();
|
|
1322
1465
|
let init_receipt = Engine::initialize(Box::new(backend.clone())).await?;
|
|
1323
1466
|
let engine = Engine::new(Box::new(backend)).await?;
|
|
@@ -1327,9 +1470,9 @@ mod tests {
|
|
|
1327
1470
|
.execute(
|
|
1328
1471
|
"INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
|
|
1329
1472
|
VALUES (\
|
|
1330
|
-
lix_json('{\"x-lix-key\":\"test_state_schema\",\"
|
|
1473
|
+
lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"},\"count\":{\"type\":\"integer\"}},\"required\":[\"value\",\"count\"],\"additionalProperties\":false}'),\
|
|
1331
1474
|
false,\
|
|
1332
|
-
|
|
1475
|
+
false\
|
|
1333
1476
|
)",
|
|
1334
1477
|
&[],
|
|
1335
1478
|
)
|
|
@@ -1337,8 +1480,8 @@ mod tests {
|
|
|
1337
1480
|
session
|
|
1338
1481
|
.execute(
|
|
1339
1482
|
"INSERT INTO test_state_schema \
|
|
1340
|
-
|
|
1341
|
-
|
|
1483
|
+
(lixcol_entity_id, value, count, lixcol_metadata, lixcol_untracked) \
|
|
1484
|
+
VALUES (lix_json('[\"entity-history\"]'), 'A', 7, '{\"source\":\"history\"}', false)",
|
|
1342
1485
|
&[],
|
|
1343
1486
|
)
|
|
1344
1487
|
.await?;
|
|
@@ -1616,23 +1759,23 @@ mod tests {
|
|
|
1616
1759
|
|
|
1617
1760
|
#[tokio::test]
|
|
1618
1761
|
async fn execute_sql_reads_lix_state_history_from_history_context() {
|
|
1619
|
-
let (session, head_commit_id) =
|
|
1762
|
+
let (session, head_commit_id) = setup_engine_history_fixture()
|
|
1620
1763
|
.await
|
|
1621
1764
|
.expect("history fixture should initialize");
|
|
1622
1765
|
let result = session
|
|
1623
1766
|
.execute(
|
|
1624
1767
|
&format!(
|
|
1625
1768
|
"SELECT entity_id, snapshot_content, metadata, depth, start_commit_id \
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1769
|
+
FROM lix_state_history \
|
|
1770
|
+
WHERE schema_key = 'test_state_schema' \
|
|
1771
|
+
AND entity_id = lix_json('[\"entity-history\"]') \
|
|
1772
|
+
AND start_commit_id = '{head_commit_id}' \
|
|
1773
|
+
AND depth >= 0"
|
|
1631
1774
|
),
|
|
1632
1775
|
&[],
|
|
1633
1776
|
)
|
|
1634
1777
|
.await
|
|
1635
|
-
.expect("sql2 execute should read lix_state_history through real
|
|
1778
|
+
.expect("sql2 execute should read lix_state_history through real engine context");
|
|
1636
1779
|
let (columns, rows) = rows_from_execute_result(result);
|
|
1637
1780
|
|
|
1638
1781
|
assert_eq!(
|
|
@@ -1646,7 +1789,7 @@ mod tests {
|
|
|
1646
1789
|
]
|
|
1647
1790
|
);
|
|
1648
1791
|
assert_eq!(rows.len(), 1);
|
|
1649
|
-
assert_eq!(rows[0][0], Value::
|
|
1792
|
+
assert_eq!(rows[0][0], Value::Json(json!(["entity-history"])));
|
|
1650
1793
|
assert_eq!(rows[0][1], Value::Json(json!({"count": 7, "value": "A"})));
|
|
1651
1794
|
assert_eq!(rows[0][2], Value::Json(json!({"source": "history"})));
|
|
1652
1795
|
assert!(matches!(rows[0][3], Value::Integer(_)));
|
|
@@ -1655,21 +1798,21 @@ mod tests {
|
|
|
1655
1798
|
|
|
1656
1799
|
#[tokio::test]
|
|
1657
1800
|
async fn execute_sql_reads_entity_history_view_from_history_context() {
|
|
1658
|
-
let (session, head_commit_id) =
|
|
1801
|
+
let (session, head_commit_id) = setup_engine_history_fixture()
|
|
1659
1802
|
.await
|
|
1660
1803
|
.expect("history fixture should initialize");
|
|
1661
1804
|
let result = session
|
|
1662
1805
|
.execute(
|
|
1663
1806
|
&format!(
|
|
1664
1807
|
"SELECT value, count, lixcol_entity_id, lixcol_start_commit_id, lixcol_depth \
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1808
|
+
FROM test_state_schema_history \
|
|
1809
|
+
WHERE lixcol_start_commit_id = '{head_commit_id}' \
|
|
1810
|
+
AND lixcol_entity_id = lix_json('[\"entity-history\"]')"
|
|
1668
1811
|
),
|
|
1669
1812
|
&[],
|
|
1670
1813
|
)
|
|
1671
1814
|
.await
|
|
1672
|
-
.expect("sql2 execute should read entity history through real
|
|
1815
|
+
.expect("sql2 execute should read entity history through real engine context");
|
|
1673
1816
|
let (columns, rows) = rows_from_execute_result(result);
|
|
1674
1817
|
|
|
1675
1818
|
assert_eq!(
|
|
@@ -1685,14 +1828,14 @@ mod tests {
|
|
|
1685
1828
|
assert_eq!(rows.len(), 1);
|
|
1686
1829
|
assert_eq!(rows[0][0], Value::Text("A".to_string()));
|
|
1687
1830
|
assert_eq!(rows[0][1], Value::Integer(7));
|
|
1688
|
-
assert_eq!(rows[0][2], Value::
|
|
1831
|
+
assert_eq!(rows[0][2], Value::Json(json!(["entity-history"])));
|
|
1689
1832
|
assert_eq!(rows[0][3], Value::Text(head_commit_id));
|
|
1690
1833
|
assert!(matches!(rows[0][4], Value::Integer(_)));
|
|
1691
1834
|
}
|
|
1692
1835
|
|
|
1693
1836
|
#[tokio::test]
|
|
1694
1837
|
async fn execute_sql_reads_directory_history_view_from_history_context() {
|
|
1695
|
-
let (session, head_commit_id) =
|
|
1838
|
+
let (session, head_commit_id) = setup_engine_history_fixture()
|
|
1696
1839
|
.await
|
|
1697
1840
|
.expect("history fixture should initialize");
|
|
1698
1841
|
let result = session
|
|
@@ -1705,7 +1848,7 @@ mod tests {
|
|
|
1705
1848
|
&[],
|
|
1706
1849
|
)
|
|
1707
1850
|
.await
|
|
1708
|
-
.expect("sql2 execute should read directory history through real
|
|
1851
|
+
.expect("sql2 execute should read directory history through real engine context");
|
|
1709
1852
|
assert!(
|
|
1710
1853
|
result.notices().is_empty(),
|
|
1711
1854
|
"identity-filtered directory history should not emit soft notices"
|
|
@@ -1754,7 +1897,7 @@ mod tests {
|
|
|
1754
1897
|
|
|
1755
1898
|
#[tokio::test]
|
|
1756
1899
|
async fn execute_sql_reads_file_history_view_from_history_context() {
|
|
1757
|
-
let (session, head_commit_id) =
|
|
1900
|
+
let (session, head_commit_id) = setup_engine_history_fixture()
|
|
1758
1901
|
.await
|
|
1759
1902
|
.expect("history fixture should initialize");
|
|
1760
1903
|
let result = session
|
|
@@ -1770,7 +1913,7 @@ mod tests {
|
|
|
1770
1913
|
&[],
|
|
1771
1914
|
)
|
|
1772
1915
|
.await
|
|
1773
|
-
.expect("sql2 execute should read file history through real
|
|
1916
|
+
.expect("sql2 execute should read file history through real engine context");
|
|
1774
1917
|
assert!(
|
|
1775
1918
|
result.notices().is_empty(),
|
|
1776
1919
|
"identity-filtered file history should not emit soft notices"
|
|
@@ -1815,6 +1958,36 @@ mod tests {
|
|
|
1815
1958
|
);
|
|
1816
1959
|
}
|
|
1817
1960
|
|
|
1961
|
+
#[tokio::test]
|
|
1962
|
+
async fn execute_sql_rejects_writes_to_history_views_before_planning() {
|
|
1963
|
+
for sql in [
|
|
1964
|
+
"DELETE FROM lix_state_history",
|
|
1965
|
+
"DELETE FROM LIX_STATE_HISTORY",
|
|
1966
|
+
"DELETE FROM main.LIX_STATE_HISTORY",
|
|
1967
|
+
] {
|
|
1968
|
+
let blob_reader: Arc<dyn BlobDataReader> = Arc::new(DummyBlobReader);
|
|
1969
|
+
let live_state = Arc::new(DummyLiveStateReader);
|
|
1970
|
+
let staged_writes = Arc::new(Mutex::new(CapturingStagedWrites::default()));
|
|
1971
|
+
let mut ctx = DummySqlWriteExecutionContext {
|
|
1972
|
+
active_version_id: "version-a",
|
|
1973
|
+
blob_reader,
|
|
1974
|
+
live_state,
|
|
1975
|
+
staged_writes,
|
|
1976
|
+
schema_definitions: vec![],
|
|
1977
|
+
};
|
|
1978
|
+
|
|
1979
|
+
let error = execute_write_sql(&mut ctx, sql, &[])
|
|
1980
|
+
.await
|
|
1981
|
+
.expect_err("history views are read-only");
|
|
1982
|
+
|
|
1983
|
+
assert_eq!(error.code, LixError::CODE_READ_ONLY, "{sql}");
|
|
1984
|
+
assert_eq!(
|
|
1985
|
+
error.message, "DML cannot write read-only history view 'lix_state_history'",
|
|
1986
|
+
"{sql}"
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1818
1991
|
#[tokio::test]
|
|
1819
1992
|
async fn execute_sql_insert_into_lix_state_values_stages_write() {
|
|
1820
1993
|
let blob_reader: Arc<dyn BlobDataReader> = Arc::new(DummyBlobReader);
|
|
@@ -1829,14 +2002,14 @@ mod tests {
|
|
|
1829
2002
|
};
|
|
1830
2003
|
|
|
1831
2004
|
let result = execute_write_sql(
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2005
|
+
&mut ctx,
|
|
2006
|
+
"INSERT INTO lix_state (\
|
|
2007
|
+
entity_id, schema_key, file_id, snapshot_content, metadata, global, untracked\
|
|
2008
|
+
) VALUES (\
|
|
2009
|
+
lix_json('[\"entity-1\"]'), 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"world\"}', '{\"source\":\"sql\"}', false, false\
|
|
2010
|
+
)",
|
|
2011
|
+
&[],
|
|
2012
|
+
)
|
|
1840
2013
|
.await
|
|
1841
2014
|
.expect("INSERT INTO lix_state VALUES should stage write");
|
|
1842
2015
|
|
|
@@ -1850,8 +2023,7 @@ mod tests {
|
|
|
1850
2023
|
.expect("staged delta should expose pending overlay");
|
|
1851
2024
|
let rows = overlay.visible_semantic_rows(false, "lix_key_value");
|
|
1852
2025
|
assert_eq!(rows.len(), 1);
|
|
1853
|
-
assert_eq!(rows[0].entity_id, "entity-1");
|
|
1854
|
-
assert_eq!(rows[0].schema_version, "1");
|
|
2026
|
+
assert_eq!(rows[0].entity_id, "[\"entity-1\"]");
|
|
1855
2027
|
assert_eq!(rows[0].version_id, "version-a");
|
|
1856
2028
|
assert!(!rows[0].global);
|
|
1857
2029
|
assert!(!rows[0].untracked);
|
|
@@ -1859,7 +2031,7 @@ mod tests {
|
|
|
1859
2031
|
rows[0].snapshot_content.as_deref(),
|
|
1860
2032
|
Some("{\"key\":\"hello\",\"value\":\"world\"}")
|
|
1861
2033
|
);
|
|
1862
|
-
assert_eq!(rows[0].metadata.
|
|
2034
|
+
assert_eq!(rows[0].metadata.as_deref(), Some("{\"source\":\"sql\"}"));
|
|
1863
2035
|
}
|
|
1864
2036
|
|
|
1865
2037
|
#[tokio::test]
|
|
@@ -1876,14 +2048,14 @@ mod tests {
|
|
|
1876
2048
|
};
|
|
1877
2049
|
|
|
1878
2050
|
let result = execute_write_sql(
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2051
|
+
&mut ctx,
|
|
2052
|
+
"INSERT INTO lix_state (\
|
|
2053
|
+
entity_id, schema_key, file_id, snapshot_content, metadata\
|
|
2054
|
+
) VALUES (\
|
|
2055
|
+
lix_json('[\"entity-defaults\"]'), 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"defaults\"}', NULL\
|
|
2056
|
+
)",
|
|
2057
|
+
&[],
|
|
2058
|
+
)
|
|
1887
2059
|
.await
|
|
1888
2060
|
.expect("INSERT INTO lix_state should default bookkeeping flags");
|
|
1889
2061
|
|
|
@@ -1897,7 +2069,7 @@ mod tests {
|
|
|
1897
2069
|
.expect("staged delta should expose pending overlay");
|
|
1898
2070
|
let rows = overlay.visible_semantic_rows(false, "lix_key_value");
|
|
1899
2071
|
assert_eq!(rows.len(), 1);
|
|
1900
|
-
assert_eq!(rows[0].entity_id, "entity-defaults");
|
|
2072
|
+
assert_eq!(rows[0].entity_id, "[\"entity-defaults\"]");
|
|
1901
2073
|
assert_eq!(rows[0].version_id, "version-a");
|
|
1902
2074
|
assert!(!rows[0].global);
|
|
1903
2075
|
assert!(!rows[0].untracked);
|
|
@@ -1919,15 +2091,14 @@ mod tests {
|
|
|
1919
2091
|
let result = execute_write_sql(
|
|
1920
2092
|
&mut ctx,
|
|
1921
2093
|
"INSERT INTO lix_state (\
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
2094
|
+
entity_id, schema_key, file_id, snapshot_content, metadata, global, untracked\
|
|
2095
|
+
) \
|
|
2096
|
+
SELECT \
|
|
2097
|
+
lix_json('[\"entity-from-select\"]') AS entity_id, \
|
|
2098
|
+
'lix_key_value' AS schema_key, \
|
|
2099
|
+
NULL AS file_id, \
|
|
1928
2100
|
'{\"key\":\"hello\",\"value\":\"from-select\"}' AS snapshot_content, \
|
|
1929
2101
|
'{\"source\":\"select\"}' AS metadata, \
|
|
1930
|
-
'1' AS schema_version, \
|
|
1931
2102
|
false AS global, \
|
|
1932
2103
|
false AS untracked",
|
|
1933
2104
|
&[],
|
|
@@ -1945,17 +2116,13 @@ mod tests {
|
|
|
1945
2116
|
.expect("staged delta should expose pending overlay");
|
|
1946
2117
|
let rows = overlay.visible_semantic_rows(false, "lix_key_value");
|
|
1947
2118
|
assert_eq!(rows.len(), 1);
|
|
1948
|
-
assert_eq!(rows[0].entity_id, "entity-from-select");
|
|
1949
|
-
assert_eq!(rows[0].schema_version, "1");
|
|
2119
|
+
assert_eq!(rows[0].entity_id, "[\"entity-from-select\"]");
|
|
1950
2120
|
assert_eq!(rows[0].version_id, "version-a");
|
|
1951
2121
|
assert_eq!(
|
|
1952
2122
|
rows[0].snapshot_content.as_deref(),
|
|
1953
2123
|
Some("{\"key\":\"hello\",\"value\":\"from-select\"}")
|
|
1954
2124
|
);
|
|
1955
|
-
assert_eq!(
|
|
1956
|
-
rows[0].metadata.as_ref(),
|
|
1957
|
-
Some(&json!({"source": "select"}))
|
|
1958
|
-
);
|
|
2125
|
+
assert_eq!(rows[0].metadata.as_deref(), Some("{\"source\":\"select\"}"));
|
|
1959
2126
|
}
|
|
1960
2127
|
|
|
1961
2128
|
#[tokio::test]
|
|
@@ -1970,7 +2137,6 @@ mod tests {
|
|
|
1970
2137
|
staged_writes: Arc::clone(&staged_writes),
|
|
1971
2138
|
schema_definitions: vec![json!({
|
|
1972
2139
|
"x-lix-key": "test_state_schema",
|
|
1973
|
-
"x-lix-version": "1",
|
|
1974
2140
|
"type": "object",
|
|
1975
2141
|
"properties": {
|
|
1976
2142
|
"value": { "type": "string" }
|
|
@@ -1981,8 +2147,8 @@ mod tests {
|
|
|
1981
2147
|
let result = execute_write_sql(
|
|
1982
2148
|
&mut ctx,
|
|
1983
2149
|
"INSERT INTO test_state_schema_by_version (\
|
|
1984
|
-
|
|
1985
|
-
|
|
2150
|
+
lixcol_entity_id, lixcol_version_id, value\
|
|
2151
|
+
) VALUES (lix_json('[\"entity-c\"]'), 'version-b', 'C')",
|
|
1986
2152
|
&[],
|
|
1987
2153
|
)
|
|
1988
2154
|
.await
|
|
@@ -1998,8 +2164,7 @@ mod tests {
|
|
|
1998
2164
|
.expect("staged delta should expose pending overlay");
|
|
1999
2165
|
let rows = overlay.visible_semantic_rows(false, "test_state_schema");
|
|
2000
2166
|
assert_eq!(rows.len(), 1);
|
|
2001
|
-
assert_eq!(rows[0].entity_id, "entity-c");
|
|
2002
|
-
assert_eq!(rows[0].schema_version, "1");
|
|
2167
|
+
assert_eq!(rows[0].entity_id, "[\"entity-c\"]");
|
|
2003
2168
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2004
2169
|
assert!(!rows[0].global);
|
|
2005
2170
|
assert!(!rows[0].untracked);
|
|
@@ -2021,7 +2186,6 @@ mod tests {
|
|
|
2021
2186
|
staged_writes: Arc::clone(&staged_writes),
|
|
2022
2187
|
schema_definitions: vec![json!({
|
|
2023
2188
|
"x-lix-key": "test_state_schema",
|
|
2024
|
-
"x-lix-version": "1",
|
|
2025
2189
|
"type": "object",
|
|
2026
2190
|
"properties": {
|
|
2027
2191
|
"value": { "type": "string" }
|
|
@@ -2032,7 +2196,7 @@ mod tests {
|
|
|
2032
2196
|
let result = execute_write_sql(
|
|
2033
2197
|
&mut ctx,
|
|
2034
2198
|
"INSERT INTO test_state_schema (lixcol_entity_id, value) \
|
|
2035
|
-
|
|
2199
|
+
VALUES (lix_json('[\"entity-c\"]'), 'C')",
|
|
2036
2200
|
&[],
|
|
2037
2201
|
)
|
|
2038
2202
|
.await
|
|
@@ -2048,7 +2212,7 @@ mod tests {
|
|
|
2048
2212
|
.expect("staged delta should expose pending overlay");
|
|
2049
2213
|
let rows = overlay.visible_semantic_rows(false, "test_state_schema");
|
|
2050
2214
|
assert_eq!(rows.len(), 1);
|
|
2051
|
-
assert_eq!(rows[0].entity_id, "entity-c");
|
|
2215
|
+
assert_eq!(rows[0].entity_id, "[\"entity-c\"]");
|
|
2052
2216
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2053
2217
|
assert!(!rows[0].global);
|
|
2054
2218
|
assert!(!rows[0].untracked);
|
|
@@ -2091,8 +2255,7 @@ mod tests {
|
|
|
2091
2255
|
.expect("staged delta should expose pending overlay");
|
|
2092
2256
|
let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
|
|
2093
2257
|
assert_eq!(rows.len(), 1);
|
|
2094
|
-
assert_eq!(rows[0].entity_id, "dir-docs");
|
|
2095
|
-
assert_eq!(rows[0].schema_version, "1");
|
|
2258
|
+
assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
|
|
2096
2259
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2097
2260
|
assert!(!rows[0].global);
|
|
2098
2261
|
assert!(!rows[0].untracked);
|
|
@@ -2134,7 +2297,7 @@ mod tests {
|
|
|
2134
2297
|
.expect("staged delta should expose pending overlay");
|
|
2135
2298
|
let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
|
|
2136
2299
|
assert_eq!(rows.len(), 1);
|
|
2137
|
-
assert_eq!(rows[0].entity_id, "dir-docs");
|
|
2300
|
+
assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
|
|
2138
2301
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2139
2302
|
assert!(!rows[0].global);
|
|
2140
2303
|
assert!(!rows[0].untracked);
|
|
@@ -2178,15 +2341,15 @@ mod tests {
|
|
|
2178
2341
|
.expect("staged delta should expose pending overlay");
|
|
2179
2342
|
let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
|
|
2180
2343
|
assert_eq!(rows.len(), 1);
|
|
2181
|
-
assert_eq!(rows[0].entity_id, "dir-docs");
|
|
2344
|
+
assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
|
|
2182
2345
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2183
2346
|
assert_eq!(
|
|
2184
2347
|
rows[0].snapshot_content.as_deref(),
|
|
2185
2348
|
Some("{\"hidden\":true,\"id\":\"dir-docs\",\"name\":\"docs\",\"parent_id\":null}")
|
|
2186
2349
|
);
|
|
2187
2350
|
assert_eq!(
|
|
2188
|
-
rows[0].metadata.
|
|
2189
|
-
Some(
|
|
2351
|
+
rows[0].metadata.as_deref(),
|
|
2352
|
+
Some("{\"source\":\"directory-update\"}")
|
|
2190
2353
|
);
|
|
2191
2354
|
}
|
|
2192
2355
|
|
|
@@ -2267,7 +2430,7 @@ mod tests {
|
|
|
2267
2430
|
.expect("staged delta should expose pending overlay");
|
|
2268
2431
|
let rows = overlay.visible_all_semantic_rows();
|
|
2269
2432
|
assert_eq!(rows.len(), 1);
|
|
2270
|
-
assert_eq!(rows[0].entity_id, "dir-guides");
|
|
2433
|
+
assert_eq!(rows[0].entity_id, "[\"dir-guides\"]");
|
|
2271
2434
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2272
2435
|
assert!(rows[0].tombstone);
|
|
2273
2436
|
assert_eq!(rows[0].snapshot_content, None);
|
|
@@ -2306,8 +2469,7 @@ mod tests {
|
|
|
2306
2469
|
.expect("staged delta should expose pending overlay");
|
|
2307
2470
|
let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
|
|
2308
2471
|
assert_eq!(rows.len(), 1);
|
|
2309
|
-
assert_eq!(rows[0].entity_id, "file-readme");
|
|
2310
|
-
assert_eq!(rows[0].schema_version, "1");
|
|
2472
|
+
assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
|
|
2311
2473
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2312
2474
|
assert!(!rows[0].global);
|
|
2313
2475
|
assert!(!rows[0].untracked);
|
|
@@ -2352,7 +2514,7 @@ mod tests {
|
|
|
2352
2514
|
.expect("staged delta should expose pending overlay");
|
|
2353
2515
|
let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
|
|
2354
2516
|
assert_eq!(rows.len(), 1);
|
|
2355
|
-
assert_eq!(rows[0].entity_id, "file-readme");
|
|
2517
|
+
assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
|
|
2356
2518
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2357
2519
|
assert!(!rows[0].global);
|
|
2358
2520
|
assert!(!rows[0].untracked);
|
|
@@ -2391,10 +2553,10 @@ mod tests {
|
|
|
2391
2553
|
.expect("staged delta should expose pending overlay");
|
|
2392
2554
|
let descriptor_rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
|
|
2393
2555
|
assert_eq!(descriptor_rows.len(), 1);
|
|
2394
|
-
assert_eq!(descriptor_rows[0].entity_id, "file-readme");
|
|
2556
|
+
assert_eq!(descriptor_rows[0].entity_id, "[\"file-readme\"]");
|
|
2395
2557
|
let blob_ref_rows = overlay.visible_semantic_rows(false, "lix_binary_blob_ref");
|
|
2396
2558
|
assert_eq!(blob_ref_rows.len(), 1);
|
|
2397
|
-
assert_eq!(blob_ref_rows[0].entity_id, "file-readme");
|
|
2559
|
+
assert_eq!(blob_ref_rows[0].entity_id, "[\"file-readme\"]");
|
|
2398
2560
|
assert_eq!(blob_ref_rows[0].file_id.as_deref(), Some("file-readme"));
|
|
2399
2561
|
assert_eq!(blob_ref_rows[0].version_id, "version-b");
|
|
2400
2562
|
let snapshot: JsonValue =
|
|
@@ -2458,7 +2620,7 @@ mod tests {
|
|
|
2458
2620
|
.expect("staged delta should expose pending overlay");
|
|
2459
2621
|
let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
|
|
2460
2622
|
assert_eq!(rows.len(), 1);
|
|
2461
|
-
assert_eq!(rows[0].entity_id, "file-readme");
|
|
2623
|
+
assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
|
|
2462
2624
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2463
2625
|
let snapshot: JsonValue =
|
|
2464
2626
|
serde_json::from_str(rows[0].snapshot_content.as_deref().unwrap())
|
|
@@ -2468,8 +2630,8 @@ mod tests {
|
|
|
2468
2630
|
assert_eq!(snapshot["name"], "readme-updated.txt");
|
|
2469
2631
|
assert_eq!(snapshot["hidden"], true);
|
|
2470
2632
|
assert_eq!(
|
|
2471
|
-
rows[0].metadata.
|
|
2472
|
-
Some(
|
|
2633
|
+
rows[0].metadata.as_deref(),
|
|
2634
|
+
Some("{\"source\":\"file-update\"}")
|
|
2473
2635
|
);
|
|
2474
2636
|
}
|
|
2475
2637
|
|
|
@@ -2518,7 +2680,7 @@ mod tests {
|
|
|
2518
2680
|
.is_empty());
|
|
2519
2681
|
let blob_ref_rows = overlay.visible_semantic_rows(false, "lix_binary_blob_ref");
|
|
2520
2682
|
assert_eq!(blob_ref_rows.len(), 1);
|
|
2521
|
-
assert_eq!(blob_ref_rows[0].entity_id, "file-readme");
|
|
2683
|
+
assert_eq!(blob_ref_rows[0].entity_id, "[\"file-readme\"]");
|
|
2522
2684
|
let snapshot: JsonValue =
|
|
2523
2685
|
serde_json::from_str(blob_ref_rows[0].snapshot_content.as_deref().unwrap())
|
|
2524
2686
|
.expect("blob ref snapshot JSON");
|
|
@@ -2626,7 +2788,7 @@ mod tests {
|
|
|
2626
2788
|
.expect("staged delta should expose pending overlay");
|
|
2627
2789
|
let rows = overlay.visible_all_semantic_rows();
|
|
2628
2790
|
assert_eq!(rows.len(), 1);
|
|
2629
|
-
assert_eq!(rows[0].entity_id, "file-guide");
|
|
2791
|
+
assert_eq!(rows[0].entity_id, "[\"file-guide\"]");
|
|
2630
2792
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2631
2793
|
assert!(rows[0].tombstone);
|
|
2632
2794
|
assert_eq!(rows[0].snapshot_content, None);
|
|
@@ -2649,7 +2811,6 @@ mod tests {
|
|
|
2649
2811
|
staged_writes: Arc::clone(&staged_writes),
|
|
2650
2812
|
schema_definitions: vec![json!({
|
|
2651
2813
|
"x-lix-key": "test_state_schema",
|
|
2652
|
-
"x-lix-version": "1",
|
|
2653
2814
|
"type": "object",
|
|
2654
2815
|
"properties": {
|
|
2655
2816
|
"value": { "type": "string" }
|
|
@@ -2677,15 +2838,15 @@ mod tests {
|
|
|
2677
2838
|
.expect("staged delta should expose pending overlay");
|
|
2678
2839
|
let rows = overlay.visible_semantic_rows(false, "test_state_schema");
|
|
2679
2840
|
assert_eq!(rows.len(), 1);
|
|
2680
|
-
assert_eq!(rows[0].entity_id, "entity-a");
|
|
2841
|
+
assert_eq!(rows[0].entity_id, "[\"entity-a\"]");
|
|
2681
2842
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2682
2843
|
assert_eq!(
|
|
2683
2844
|
rows[0].snapshot_content.as_deref(),
|
|
2684
2845
|
Some("{\"value\":\"updated\"}")
|
|
2685
2846
|
);
|
|
2686
2847
|
assert_eq!(
|
|
2687
|
-
rows[0].metadata.
|
|
2688
|
-
Some(
|
|
2848
|
+
rows[0].metadata.as_deref(),
|
|
2849
|
+
Some("{\"source\":\"entity-update\"}")
|
|
2689
2850
|
);
|
|
2690
2851
|
}
|
|
2691
2852
|
|
|
@@ -2706,7 +2867,6 @@ mod tests {
|
|
|
2706
2867
|
staged_writes: Arc::clone(&staged_writes),
|
|
2707
2868
|
schema_definitions: vec![json!({
|
|
2708
2869
|
"x-lix-key": "test_state_schema",
|
|
2709
|
-
"x-lix-version": "1",
|
|
2710
2870
|
"type": "object",
|
|
2711
2871
|
"properties": {
|
|
2712
2872
|
"value": { "type": "string" }
|
|
@@ -2733,7 +2893,7 @@ mod tests {
|
|
|
2733
2893
|
.expect("staged delta should expose pending overlay");
|
|
2734
2894
|
let rows = overlay.visible_all_semantic_rows();
|
|
2735
2895
|
assert_eq!(rows.len(), 1);
|
|
2736
|
-
assert_eq!(rows[0].entity_id, "entity-b");
|
|
2896
|
+
assert_eq!(rows[0].entity_id, "[\"entity-b\"]");
|
|
2737
2897
|
assert_eq!(rows[0].version_id, "version-b");
|
|
2738
2898
|
assert!(rows[0].tombstone);
|
|
2739
2899
|
assert_eq!(rows[0].snapshot_content, None);
|
|
@@ -2762,7 +2922,7 @@ mod tests {
|
|
|
2762
2922
|
"UPDATE lix_state \
|
|
2763
2923
|
SET snapshot_content = '{\"key\":\"hello\",\"value\":\"updated\"}', \
|
|
2764
2924
|
metadata = '{\"schema_key\":\"lix_key_value\"}' \
|
|
2765
|
-
WHERE metadata = '{\"source\":\"match\"}'",
|
|
2925
|
+
WHERE metadata = lix_json('{\"source\":\"match\"}')",
|
|
2766
2926
|
&[],
|
|
2767
2927
|
)
|
|
2768
2928
|
.await
|
|
@@ -2778,15 +2938,15 @@ mod tests {
|
|
|
2778
2938
|
.expect("staged delta should expose pending overlay");
|
|
2779
2939
|
let rows = overlay.visible_semantic_rows(false, "lix_key_value");
|
|
2780
2940
|
assert_eq!(rows.len(), 1);
|
|
2781
|
-
assert_eq!(rows[0].entity_id, "entity-1");
|
|
2941
|
+
assert_eq!(rows[0].entity_id, "[\"entity-1\"]");
|
|
2782
2942
|
assert_eq!(rows[0].version_id, "version-a");
|
|
2783
2943
|
assert_eq!(
|
|
2784
2944
|
rows[0].snapshot_content.as_deref(),
|
|
2785
2945
|
Some("{\"key\":\"hello\",\"value\":\"updated\"}")
|
|
2786
2946
|
);
|
|
2787
2947
|
assert_eq!(
|
|
2788
|
-
rows[0].metadata.
|
|
2789
|
-
Some(
|
|
2948
|
+
rows[0].metadata.as_deref(),
|
|
2949
|
+
Some("{\"schema_key\":\"lix_key_value\"}")
|
|
2790
2950
|
);
|
|
2791
2951
|
}
|
|
2792
2952
|
|
|
@@ -2824,8 +2984,8 @@ mod tests {
|
|
|
2824
2984
|
assert_eq!(rows.len(), 2);
|
|
2825
2985
|
assert!(rows.iter().all(|row| row.tombstone));
|
|
2826
2986
|
assert!(rows.iter().all(|row| row.snapshot_content.is_none()));
|
|
2827
|
-
assert!(rows.iter().any(|row| row.entity_id == "entity-1"));
|
|
2828
|
-
assert!(rows.iter().any(|row| row.entity_id == "entity-2"));
|
|
2987
|
+
assert!(rows.iter().any(|row| row.entity_id == "[\"entity-1\"]"));
|
|
2988
|
+
assert!(rows.iter().any(|row| row.entity_id == "[\"entity-2\"]"));
|
|
2829
2989
|
}
|
|
2830
2990
|
|
|
2831
2991
|
struct BackendSqlExecutionContext<'a> {
|
|
@@ -2853,13 +3013,11 @@ mod tests {
|
|
|
2853
3013
|
Arc::clone(&self.blob_reader)
|
|
2854
3014
|
}
|
|
2855
3015
|
|
|
2856
|
-
fn
|
|
3016
|
+
fn commit_store_query_source(&self) -> SqlCommitStoreQuerySource {
|
|
2857
3017
|
let base_scope = test_read_scope(self.storage.clone());
|
|
2858
3018
|
let read_scope = StorageReadScope::new(base_scope.store());
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
crate::changelog::ChangelogContext::new().reader(read_scope.store()),
|
|
2862
|
-
),
|
|
3019
|
+
CommitStoreQuerySource {
|
|
3020
|
+
commit_store_reader: Arc::new(CommitStoreContext::new().reader(read_scope.store())),
|
|
2863
3021
|
json_reader: JsonStoreContext::new().reader(read_scope.store()),
|
|
2864
3022
|
}
|
|
2865
3023
|
}
|
|
@@ -2891,26 +3049,23 @@ mod tests {
|
|
|
2891
3049
|
crate::untracked_state::UntrackedStateContext::new(),
|
|
2892
3050
|
));
|
|
2893
3051
|
let mut writes = StorageWriteSet::new();
|
|
2894
|
-
let canonical_rows =
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
]
|
|
2912
|
-
};
|
|
2913
|
-
version_ctx.stage_canonical_ref_rows(&mut writes, &canonical_rows)?;
|
|
3052
|
+
let canonical_rows = vec![
|
|
3053
|
+
prepare_version_ref_row(
|
|
3054
|
+
"version-a",
|
|
3055
|
+
&init_receipt.initial_commit_id,
|
|
3056
|
+
"1970-01-01T00:00:00.000Z",
|
|
3057
|
+
)?,
|
|
3058
|
+
prepare_version_ref_row(
|
|
3059
|
+
"version-b",
|
|
3060
|
+
&init_receipt.initial_commit_id,
|
|
3061
|
+
"1970-01-01T00:00:00.000Z",
|
|
3062
|
+
)?,
|
|
3063
|
+
];
|
|
3064
|
+
let rows = canonical_rows
|
|
3065
|
+
.into_iter()
|
|
3066
|
+
.map(|prepared| prepared.row)
|
|
3067
|
+
.collect::<Vec<_>>();
|
|
3068
|
+
version_ctx.stage_canonical_ref_rows(&mut writes, &rows)?;
|
|
2914
3069
|
writes.apply(&mut transaction.as_mut()).await?;
|
|
2915
3070
|
transaction.commit().await?;
|
|
2916
3071
|
}
|
|
@@ -2919,7 +3074,6 @@ mod tests {
|
|
|
2919
3074
|
let session_b = engine.open_session("version-b").await?;
|
|
2920
3075
|
let schema_definition = json!({
|
|
2921
3076
|
"x-lix-key": "test_state_schema",
|
|
2922
|
-
"x-lix-version": "1",
|
|
2923
3077
|
"type": "object",
|
|
2924
3078
|
"properties": {
|
|
2925
3079
|
"value": { "type": "string" }
|
|
@@ -2931,9 +3085,9 @@ mod tests {
|
|
|
2931
3085
|
.execute(
|
|
2932
3086
|
"INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
|
|
2933
3087
|
VALUES (\
|
|
2934
|
-
lix_json('{\"x-lix-key\":\"test_state_schema\",\"
|
|
3088
|
+
lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
|
|
2935
3089
|
false,\
|
|
2936
|
-
|
|
3090
|
+
false\
|
|
2937
3091
|
)",
|
|
2938
3092
|
&[],
|
|
2939
3093
|
)
|
|
@@ -2942,9 +3096,9 @@ mod tests {
|
|
|
2942
3096
|
.execute(
|
|
2943
3097
|
"INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
|
|
2944
3098
|
VALUES (\
|
|
2945
|
-
lix_json('{\"x-lix-key\":\"test_state_schema\",\"
|
|
3099
|
+
lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
|
|
2946
3100
|
false,\
|
|
2947
|
-
|
|
3101
|
+
false\
|
|
2948
3102
|
)",
|
|
2949
3103
|
&[],
|
|
2950
3104
|
)
|
|
@@ -2952,32 +3106,32 @@ mod tests {
|
|
|
2952
3106
|
session_a
|
|
2953
3107
|
.execute(
|
|
2954
3108
|
"INSERT INTO lix_state (\
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
3109
|
+
entity_id, schema_key, file_id, snapshot_content, global, untracked\
|
|
3110
|
+
) VALUES (\
|
|
3111
|
+
lix_json('[\"entity-a\"]'), 'test_state_schema', NULL, '{\"value\":\"A\"}', false, false\
|
|
3112
|
+
)",
|
|
2959
3113
|
&[],
|
|
2960
3114
|
)
|
|
2961
3115
|
.await?;
|
|
2962
3116
|
session_b
|
|
2963
3117
|
.execute(
|
|
2964
3118
|
"INSERT INTO lix_state (\
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3119
|
+
entity_id, schema_key, file_id, snapshot_content, global, untracked\
|
|
3120
|
+
) VALUES (\
|
|
3121
|
+
lix_json('[\"entity-b\"]'), 'test_state_schema', NULL, '{\"value\":\"B\"}', false, false\
|
|
3122
|
+
)",
|
|
2969
3123
|
&[],
|
|
2970
3124
|
)
|
|
2971
3125
|
.await?;
|
|
2972
3126
|
session_a
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3127
|
+
.execute(
|
|
3128
|
+
"INSERT INTO lix_state (\
|
|
3129
|
+
entity_id, schema_key, file_id, snapshot_content, global, untracked\
|
|
3130
|
+
) VALUES (\
|
|
3131
|
+
lix_json('[\"dir-docs\"]'), 'lix_directory_descriptor', NULL, '{\"id\":\"dir-docs\",\"parent_id\":null,\"name\":\"docs\",\"hidden\":false}', false, false\
|
|
3132
|
+
)",
|
|
3133
|
+
&[],
|
|
3134
|
+
)
|
|
2981
3135
|
.await?;
|
|
2982
3136
|
session_a
|
|
2983
3137
|
.execute(
|
|
@@ -2993,7 +3147,7 @@ mod tests {
|
|
|
2993
3147
|
LiveStateContext::new(
|
|
2994
3148
|
TrackedStateContext::new(),
|
|
2995
3149
|
UntrackedStateContext::new(),
|
|
2996
|
-
crate::commit_graph::CommitGraphContext::new(
|
|
3150
|
+
crate::commit_graph::CommitGraphContext::new(),
|
|
2997
3151
|
)
|
|
2998
3152
|
}
|
|
2999
3153
|
|
|
@@ -3050,7 +3204,7 @@ mod tests {
|
|
|
3050
3204
|
vec!["entity_id", "version_id", "snapshot_content", "commit_id"]
|
|
3051
3205
|
);
|
|
3052
3206
|
assert_eq!(result.rows.len(), 1);
|
|
3053
|
-
assert_eq!(result.rows[0][0], Value::
|
|
3207
|
+
assert_eq!(result.rows[0][0], Value::Json(json!(["entity-b"])));
|
|
3054
3208
|
assert_eq!(result.rows[0][1], Value::Text("version-b".to_string()));
|
|
3055
3209
|
assert_eq!(result.rows[0][2], Value::Json(json!({"value": "B"})));
|
|
3056
3210
|
match &result.rows[0][3] {
|
|
@@ -3090,11 +3244,11 @@ mod tests {
|
|
|
3090
3244
|
.expect("broad by-version read should succeed");
|
|
3091
3245
|
|
|
3092
3246
|
assert!(
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3247
|
+
result.rows.iter().any(|row| row[0] == Value::Json(json!(["entity-a"])))
|
|
3248
|
+
&& result.rows.iter().any(|row| row[0] == Value::Json(json!(["entity-b"]))),
|
|
3249
|
+
"expected broad by-version read to include rows from multiple visible versions: {:?}",
|
|
3250
|
+
result.rows
|
|
3251
|
+
);
|
|
3098
3252
|
})
|
|
3099
3253
|
});
|
|
3100
3254
|
}
|
|
@@ -3131,7 +3285,7 @@ mod tests {
|
|
|
3131
3285
|
|
|
3132
3286
|
assert_eq!(result.columns, vec!["entity_id", "snapshot_content"]);
|
|
3133
3287
|
assert_eq!(result.rows.len(), 1);
|
|
3134
|
-
assert_eq!(result.rows[0][0], Value::
|
|
3288
|
+
assert_eq!(result.rows[0][0], Value::Json(json!(["entity-a"])));
|
|
3135
3289
|
assert_eq!(result.rows[0][1], Value::Json(json!({"value": "A"})));
|
|
3136
3290
|
})
|
|
3137
3291
|
});
|
|
@@ -3169,7 +3323,7 @@ mod tests {
|
|
|
3169
3323
|
assert_eq!(result.columns, vec!["value", "lixcol_entity_id"]);
|
|
3170
3324
|
assert_eq!(result.rows.len(), 1);
|
|
3171
3325
|
assert_eq!(result.rows[0][0], Value::Text("A".to_string()));
|
|
3172
|
-
assert_eq!(result.rows[0][1], Value::
|
|
3326
|
+
assert_eq!(result.rows[0][1], Value::Json(json!(["entity-a"])));
|
|
3173
3327
|
})
|
|
3174
3328
|
});
|
|
3175
3329
|
}
|