mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(neo4j): pick node identity function by server version
This commit is contained in:
@@ -3895,6 +3895,7 @@ async function refreshSavedRows(request: { dirtyRows: ReadonlyMap<number, Readon
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
driverProfile: connectionStore.getConfig(connectionId)?.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(connectionId)?.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(connectionId),
|
||||
catalog: tableMeta.catalog,
|
||||
database: tableMeta.database,
|
||||
@@ -6547,6 +6548,7 @@ async function applyOrderBySearch() {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
driverProfile: props.connectionId ? connectionStore.getConfig(props.connectionId)?.driver_profile : undefined,
|
||||
serverVersion: props.connectionId ? connectionStore.getConfig(props.connectionId)?.database_info?.productVersion : undefined,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId),
|
||||
catalog: tableMeta.catalog,
|
||||
database: tableMeta.database,
|
||||
@@ -6586,6 +6588,7 @@ async function applyWhereFilter() {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
driverProfile: props.connectionId ? connectionStore.getConfig(props.connectionId)?.driver_profile : undefined,
|
||||
serverVersion: props.connectionId ? connectionStore.getConfig(props.connectionId)?.database_info?.productVersion : undefined,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId),
|
||||
catalog: tableMeta.catalog,
|
||||
database: tableMeta.database,
|
||||
@@ -7851,6 +7854,7 @@ async function syncUserFacingSql() {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
driverProfile: config?.driver_profile,
|
||||
serverVersion: config?.database_info?.productVersion,
|
||||
identifierQuote: props.connectionId ? connectionStore.connectionIdentifierQuote?.(props.connectionId) : undefined,
|
||||
catalog: props.tableMeta.catalog,
|
||||
database: props.tableMeta.database,
|
||||
@@ -11341,6 +11345,7 @@ async function loadForeignKeyDisplayLabels() {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
driverProfile: connectionStore.getConfig(props.connectionId!)?.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(props.connectionId!)?.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId),
|
||||
catalog: props.tableMeta?.catalog,
|
||||
database: props.database,
|
||||
|
||||
@@ -1692,6 +1692,7 @@ async function openNewQuery(row: ObjectBrowserRow) {
|
||||
await buildTableSelectSql({
|
||||
databaseType: effectiveDatabaseType.value,
|
||||
driverProfile: props.connection.driver_profile,
|
||||
serverVersion: props.connection.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connection.id),
|
||||
catalog: props.catalog,
|
||||
database: props.database,
|
||||
|
||||
+6
@@ -97,4 +97,10 @@ describe("useDataGridEditor save SQL database qualification", () => {
|
||||
await createEditor(false).saveChanges();
|
||||
expect(mocks.prepareDataGridSave.mock.calls[1]![0]).toMatchObject({ includeDatabaseName: false });
|
||||
});
|
||||
|
||||
it("forwards the connected server version so saved statements match the read SQL (#10503)", async () => {
|
||||
mocks.getConfig.mockReturnValue({ database_info: { productVersion: "Neo4j/4.4.44" } });
|
||||
await createEditor(false).saveChanges();
|
||||
expect(mocks.prepareDataGridSave.mock.calls[0]![0]).toMatchObject({ serverVersion: "Neo4j/4.4.44" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ const mocks = vi.hoisted(() => ({
|
||||
setErrorResult: vi.fn(),
|
||||
setTableMeta: vi.fn(),
|
||||
updateSql: vi.fn(),
|
||||
databaseInfo: undefined as { productName?: string; productVersion?: string } | undefined,
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
@@ -27,7 +28,7 @@ vi.mock("@/lib/backend/api", () => ({
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => ({
|
||||
activeConnectionId: "",
|
||||
getConfig: () => ({ id: "connection-1", db_type: mocks.databaseType }),
|
||||
getConfig: () => ({ id: "connection-1", db_type: mocks.databaseType, database_info: mocks.databaseInfo }),
|
||||
ensureConnected: mocks.ensureConnected,
|
||||
connectionIdentifierQuote: () => undefined,
|
||||
metadataGenerationFor: () => 0,
|
||||
@@ -108,6 +109,7 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
mocks.tabs.length = 0;
|
||||
mocks.databaseType = "postgres";
|
||||
mocks.tableOpenSortMode = "none";
|
||||
mocks.databaseInfo = undefined;
|
||||
mocks.ensureConnected.mockResolvedValue(undefined);
|
||||
mocks.buildTableSelectSql.mockImplementation(async ({ tableName }: { tableName: string }) => `SELECT * FROM ${tableName}`);
|
||||
mocks.executeTabSql.mockResolvedValue(undefined);
|
||||
@@ -130,6 +132,30 @@ describe("useNavigationTargets openTableTarget", () => {
|
||||
expect(mocks.tabs[0]?.orderByInput).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards the connected server version to the table SQL builder (#10503)", async () => {
|
||||
mocks.databaseType = "neo4j";
|
||||
mocks.databaseInfo = { productName: "Neo4j", productVersion: "Neo4j/4.4.44" };
|
||||
|
||||
await useNavigationTargets(dialogs).openTableTarget({ connectionId: "connection-1", database: "neo4j", tableName: "users", tableType: "TABLE" });
|
||||
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ serverVersion: "Neo4j/4.4.44" }));
|
||||
});
|
||||
|
||||
it("reads the server version when the SQL is built, not from the config captured before connect (#10503)", async () => {
|
||||
mocks.databaseType = "neo4j";
|
||||
mocks.databaseInfo = undefined;
|
||||
// The store swaps the config object when the probe lands, so the reference captured before
|
||||
// connect never sees the version.
|
||||
mocks.getColumns.mockImplementation(async () => {
|
||||
mocks.databaseInfo = { productName: "Neo4j", productVersion: "Neo4j/4.4.44" };
|
||||
return [column("id")];
|
||||
});
|
||||
|
||||
await useNavigationTargets(dialogs).openTableTarget({ connectionId: "connection-1", database: "neo4j", tableName: "users", tableType: "TABLE" });
|
||||
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ serverVersion: "Neo4j/4.4.44" }));
|
||||
});
|
||||
|
||||
it.each(["oracle", "oceanbase-oracle"])("uses the %s physical primary index for default ordering", async (databaseType) => {
|
||||
mocks.databaseType = databaseType;
|
||||
mocks.tableOpenSortMode = "database";
|
||||
|
||||
@@ -122,6 +122,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
return buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config?.driver_profile,
|
||||
serverVersion: config?.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(tab.connectionId),
|
||||
database: tableMeta?.database,
|
||||
schema: tableMeta?.schema,
|
||||
|
||||
@@ -1592,6 +1592,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
if (!tableMeta.value) return null;
|
||||
return {
|
||||
databaseType: resolvedDatabaseType.value,
|
||||
serverVersion: connectionStore.getConfig(connectionId.value ?? "")?.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(connectionId.value),
|
||||
tableMeta: tableMeta.value,
|
||||
columns: result.value.columns,
|
||||
|
||||
@@ -258,6 +258,7 @@ export function useDataGridLargeValues(options: UseDataGridLargeValuesOptions) {
|
||||
if (!visibleLargeValuePreviewActive || generation !== visibleLargeValuePreviewRequestedGeneration || options.result.value !== sourceResult) return;
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: options.databaseType.value,
|
||||
serverVersion: options.getConnectionConfig(options.connectionId.value!)?.database_info?.productVersion,
|
||||
identifierQuote: options.connectionIdentifierQuote(options.connectionId.value),
|
||||
database: tableMeta.database,
|
||||
schema: tableMeta.schema,
|
||||
@@ -426,6 +427,7 @@ export function useDataGridLargeValues(options: UseDataGridLargeValuesOptions) {
|
||||
const selectedColumns = [...new Set([...tableMeta.primaryKeys, sourceColumn])];
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: options.databaseType.value,
|
||||
serverVersion: options.getConnectionConfig(options.connectionId.value!)?.database_info?.productVersion,
|
||||
identifierQuote: options.connectionIdentifierQuote(options.connectionId.value),
|
||||
database: tableMeta.database,
|
||||
schema: tableMeta.schema,
|
||||
|
||||
@@ -147,6 +147,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(target.connectionId)?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
schema: tableSchema,
|
||||
catalog: target.catalog,
|
||||
@@ -204,6 +205,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(target.connectionId)?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
schema: tableSchema,
|
||||
catalog: target.catalog,
|
||||
@@ -255,6 +257,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const emptySql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(target.connectionId)?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
schema: tableSchema,
|
||||
catalog: target.catalog,
|
||||
@@ -302,6 +305,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
const newSql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(target.connectionId)?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
schema: tableSchema,
|
||||
catalog: target.catalog,
|
||||
|
||||
@@ -375,6 +375,7 @@ export function useSidebarDataOpenRuntime() {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config?.driver_profile,
|
||||
serverVersion: connectionStore.getConfig(node.connectionId)?.database_info?.productVersion,
|
||||
identifierQuote: connectionStore.connectionIdentifierQuote?.(node.connectionId),
|
||||
schema: tableSchema,
|
||||
database: node.database,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { joinedRowChanges, type JoinedWriteSource } from "../../dataGrid/joinedRowChanges";
|
||||
import { joinedRowChanges, joinedSaveOptions, type JoinedWriteSource } from "../../dataGrid/joinedRowChanges";
|
||||
|
||||
const sources: JoinedWriteSource[] = [
|
||||
{ tableKey: "users", primaryKeys: ["id"], sourceColumns: ["id", "name", undefined, undefined] },
|
||||
@@ -79,3 +79,22 @@ describe("joined row updates", () => {
|
||||
).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("joined save options", () => {
|
||||
it("keeps the connected server version so saved rows are addressed like the rows that were read", () => {
|
||||
const options = joinedSaveOptions([{ tableMeta: { tableName: "events", primaryKeys: ["id"] }, sourceColumns: ["id", "name"] }], {
|
||||
databaseType: "neo4j",
|
||||
serverVersion: "Neo4j/4.4.44",
|
||||
identifierQuote: "`",
|
||||
includeDatabaseName: false,
|
||||
columns: ["id", "name"],
|
||||
rows: [[1, "user"]],
|
||||
dirtyRows: new Map([[0, new Map([[1, "renamed"]])]]),
|
||||
newRows: [],
|
||||
deletedRows: new Set(),
|
||||
});
|
||||
expect(options).toHaveLength(1);
|
||||
expect(options[0]!.serverVersion).toBe("Neo4j/4.4.44");
|
||||
expect(options[0]!.databaseType).toBe("neo4j");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,9 @@ export interface DataGridColumnInfo {
|
||||
|
||||
export interface DataGridSaveStatementOptions {
|
||||
databaseType?: DatabaseType;
|
||||
/** Server version reported by the connection (see `BuildTableSelectSqlOptions.serverVersion`).
|
||||
* The saved statements must address rows the same way the grid read them. */
|
||||
serverVersion?: string;
|
||||
identifierQuote?: string;
|
||||
tableMeta: DataGridTableMeta;
|
||||
columns: string[];
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface JoinedSaveTarget {
|
||||
/** Feed per-source changes through the existing SQL builder, including undo SQL. */
|
||||
export function joinedSaveOptions(
|
||||
targets: JoinedSaveTarget[],
|
||||
options: Pick<DataGridSaveStatementOptions, "databaseType" | "identifierQuote" | "columns" | "includeDatabaseName"> & {
|
||||
options: Pick<DataGridSaveStatementOptions, "databaseType" | "serverVersion" | "identifierQuote" | "columns" | "includeDatabaseName"> & {
|
||||
rows: CellValue[][];
|
||||
dirtyRows: Map<number, Map<number, CellValue>>;
|
||||
newRows: CellValue[][];
|
||||
@@ -24,6 +24,7 @@ export function joinedSaveOptions(
|
||||
}));
|
||||
return joinedRowChanges(sources, options.rows, options.dirtyRows).map((update) => ({
|
||||
databaseType: options.databaseType,
|
||||
serverVersion: options.serverVersion,
|
||||
identifierQuote: options.identifierQuote,
|
||||
columns: options.columns,
|
||||
tableMeta: targets[update.sourceIndex]!.tableMeta,
|
||||
|
||||
@@ -13,6 +13,10 @@ import type { SqlSemanticToken } from "@/lib/sql/semantic/types";
|
||||
export interface BuildTableSelectSqlOptions {
|
||||
databaseType?: DatabaseType;
|
||||
driverProfile?: string;
|
||||
/** Server version reported by the connection (for example `Neo4j/4.4.44`). Engines that
|
||||
* renamed a built-in function across releases (Neo4j below 5 uses `id()` where 5+ uses
|
||||
* `elementId()`) need it to generate SQL the connected server understands. */
|
||||
serverVersion?: string;
|
||||
identifierQuote?: string;
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
|
||||
@@ -4842,6 +4842,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: conn?.driver_profile,
|
||||
serverVersion: conn?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
database: tableMeta.database,
|
||||
schema: tableMeta.schema,
|
||||
@@ -8962,6 +8963,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const sql = await api.buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: conn?.driver_profile,
|
||||
serverVersion: conn?.database_info?.productVersion,
|
||||
identifierQuote,
|
||||
database: tableMeta.database,
|
||||
schema: tableMeta.schema,
|
||||
|
||||
@@ -1491,7 +1491,8 @@ async fn execute_get_sample_data(
|
||||
|
||||
// Reuse the table-data builder so identifier quoting and row limiting follow
|
||||
// the active database instead of assuming PostgreSQL syntax.
|
||||
let sql = build_sample_data_sql(db_type, schema.as_deref(), table, limit);
|
||||
let server_version = connection_server_version(state, connection_id).await;
|
||||
let sql = build_sample_data_sql(db_type, server_version.as_deref(), schema.as_deref(), table, limit);
|
||||
|
||||
// Delegate to execute_execute_query with a synthetic tool call
|
||||
let synthetic_call = ToolCall {
|
||||
@@ -1512,9 +1513,28 @@ async fn execute_get_sample_data(
|
||||
.await
|
||||
}
|
||||
|
||||
fn build_sample_data_sql(db_type: &DatabaseType, schema: Option<&str>, table: &str, limit: usize) -> String {
|
||||
/// The sample-data statement is produced by the shared dialect code, so it has to match the
|
||||
/// connected server too: Neo4j below 5 only accepts `id()` where 5+ uses `elementId()`.
|
||||
async fn connection_server_version(state: &Arc<AppState>, connection_id: &str) -> Option<String> {
|
||||
state
|
||||
.configs
|
||||
.read()
|
||||
.await
|
||||
.get(connection_id)
|
||||
.and_then(|config| config.database_info.as_ref())
|
||||
.and_then(|info| info.product_version.clone())
|
||||
}
|
||||
|
||||
fn build_sample_data_sql(
|
||||
db_type: &DatabaseType,
|
||||
server_version: Option<&str>,
|
||||
schema: Option<&str>,
|
||||
table: &str,
|
||||
limit: usize,
|
||||
) -> String {
|
||||
build_table_data_select_sql(TableDataSelectSqlOptions {
|
||||
database_type: Some(*db_type),
|
||||
server_version: server_version.map(str::to_owned),
|
||||
schema: schema.map(str::to_owned),
|
||||
table_name: table.to_string(),
|
||||
limit: Some(limit),
|
||||
@@ -2640,23 +2660,35 @@ for line in sys.stdin:
|
||||
#[test]
|
||||
fn sample_data_sql_uses_database_identifier_and_limit_syntax() {
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::Mysql, Some("app"), "sys_tenant", 20),
|
||||
build_sample_data_sql(&DatabaseType::Mysql, None, Some("app"), "sys_tenant", 20),
|
||||
"SELECT * FROM `app`.`sys_tenant` LIMIT 20;"
|
||||
);
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::Postgres, Some("public"), "sys_tenant", 20),
|
||||
build_sample_data_sql(&DatabaseType::Postgres, None, Some("public"), "sys_tenant", 20),
|
||||
"SELECT * FROM \"public\".\"sys_tenant\" LIMIT 20;"
|
||||
);
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::SqlServer, Some("dbo"), "sys_tenant", 20),
|
||||
build_sample_data_sql(&DatabaseType::SqlServer, None, Some("dbo"), "sys_tenant", 20),
|
||||
"SELECT TOP (20) * FROM [dbo].[sys_tenant]"
|
||||
);
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::Oracle, Some("APP"), "SYS_TENANT", 20),
|
||||
build_sample_data_sql(&DatabaseType::Oracle, None, Some("APP"), "SYS_TENANT", 20),
|
||||
"SELECT * FROM (SELECT * FROM \"APP\".\"SYS_TENANT\") WHERE ROWNUM <= 20"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_data_sql_follows_the_connected_neo4j_identifier_function() {
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::Neo4j, Some("Neo4j/4.4.44"), None, "Person", 20),
|
||||
"MATCH (n:`Person`) RETURN id(n) AS `__DBX_ELEMENT_ID`, n LIMIT 20;"
|
||||
);
|
||||
assert_eq!(
|
||||
build_sample_data_sql(&DatabaseType::Neo4j, Some("Neo4j/5.26.0"), None, "Person", 20),
|
||||
"MATCH (n:`Person`) RETURN elementId(n) AS `__DBX_ELEMENT_ID`, n LIMIT 20;"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_schema_overrides_the_tool_schema_argument() {
|
||||
let call = ToolCall {
|
||||
|
||||
@@ -36,18 +36,27 @@ pub struct TableCsvExportOptions {
|
||||
pub null_literal: String,
|
||||
}
|
||||
|
||||
async fn connection_database_type(state: &AppState, connection_id: &str) -> Result<DatabaseType, String> {
|
||||
/// Export pages go through the same dialect code as the grid, so the statement has to match the
|
||||
/// connected server too: Neo4j below 5 only accepts `id()` where 5+ uses `elementId()`. That needs
|
||||
/// the recorded version next to the connection type.
|
||||
async fn connection_server_version_and_type(
|
||||
state: &AppState,
|
||||
connection_id: &str,
|
||||
) -> Result<(Option<String>, DatabaseType), String> {
|
||||
state
|
||||
.configs
|
||||
.read()
|
||||
.await
|
||||
.get(connection_id)
|
||||
.map(|config| config.db_type)
|
||||
.map(|config| {
|
||||
let server_version = config.database_info.as_ref().and_then(|info| info.product_version.clone());
|
||||
(server_version, config.db_type)
|
||||
})
|
||||
.ok_or_else(|| format!("Connection config not found: {connection_id}"))
|
||||
}
|
||||
|
||||
pub async fn export_table_data_csv_core(state: &AppState, options: TableCsvExportOptions) -> Result<u64, String> {
|
||||
let database_type = connection_database_type(state, &options.connection_id).await?;
|
||||
let (server_version, database_type) = connection_server_version_and_type(state, &options.connection_id).await?;
|
||||
// This loop pages with `LIMIT <page_size> OFFSET <n>` and stops at the first
|
||||
// short page. Neither half holds for SOQL: a `/query` response carries at most
|
||||
// 2000 rows and hands the rest back as a QueryLocator (`has_more` +
|
||||
@@ -67,6 +76,7 @@ pub async fn export_table_data_csv_core(state: &AppState, options: TableCsvExpor
|
||||
let outcome = write_table_csv_pages(
|
||||
&mut writer,
|
||||
database_type,
|
||||
server_version.as_deref(),
|
||||
&options,
|
||||
client_session_id.as_deref(),
|
||||
|sql, query_options| async move {
|
||||
@@ -93,6 +103,7 @@ pub async fn export_table_data_csv_core(state: &AppState, options: TableCsvExpor
|
||||
async fn write_table_csv_pages<Execute, QueryFuture>(
|
||||
writer: &mut impl Write,
|
||||
database_type: DatabaseType,
|
||||
server_version: Option<&str>,
|
||||
options: &TableCsvExportOptions,
|
||||
client_session_id: Option<&str>,
|
||||
mut execute_page: Execute,
|
||||
@@ -113,6 +124,7 @@ where
|
||||
loop {
|
||||
let sql = build_table_data_select_sql(TableDataSelectSqlOptions {
|
||||
database_type: Some(database_type),
|
||||
server_version: server_version.map(str::to_owned),
|
||||
schema: options.schema.clone(),
|
||||
table_name: options.table_name.clone(),
|
||||
table_type: None,
|
||||
@@ -225,6 +237,7 @@ mod tests {
|
||||
let count = write_table_csv_pages(
|
||||
&mut output,
|
||||
DatabaseType::Cassandra,
|
||||
None,
|
||||
&export_options(usize::MAX),
|
||||
Some("export-test"),
|
||||
|sql, options| {
|
||||
@@ -266,6 +279,7 @@ mod tests {
|
||||
let count = write_table_csv_pages(
|
||||
&mut Vec::new(),
|
||||
DatabaseType::Cassandra,
|
||||
None,
|
||||
&export_options(2),
|
||||
Some("export-test"),
|
||||
|_, _| {
|
||||
@@ -289,6 +303,7 @@ mod tests {
|
||||
let error = write_table_csv_pages(
|
||||
&mut Vec::new(),
|
||||
DatabaseType::Cassandra,
|
||||
None,
|
||||
&export_options(2),
|
||||
Some("export-test"),
|
||||
|_, _| {
|
||||
@@ -309,6 +324,7 @@ mod tests {
|
||||
let error = write_table_csv_pages(
|
||||
&mut Vec::new(),
|
||||
DatabaseType::Cassandra,
|
||||
None,
|
||||
&export_options(2),
|
||||
Some("export-test"),
|
||||
|_, options| {
|
||||
@@ -331,23 +347,53 @@ mod tests {
|
||||
async fn non_cassandra_csv_export_preserves_limit_offset_paging() {
|
||||
let mut pages = vec![page(&[1, 2], None, false), page(&[3], None, false)].into_iter();
|
||||
let mut sqls = Vec::new();
|
||||
let count =
|
||||
write_table_csv_pages(&mut Vec::new(), DatabaseType::Sqlite, &export_options(2), None, |sql, options| {
|
||||
let count = write_table_csv_pages(
|
||||
&mut Vec::new(),
|
||||
DatabaseType::Sqlite,
|
||||
None,
|
||||
&export_options(2),
|
||||
None,
|
||||
|sql, options| {
|
||||
sqls.push(sql);
|
||||
assert_eq!(options.max_rows, Some(2));
|
||||
assert!(options.page_size.is_none());
|
||||
assert!(options.result_session_id.is_none());
|
||||
assert!(options.client_session_id.is_none());
|
||||
std::future::ready(Ok(pages.next().unwrap()))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, 3);
|
||||
assert_eq!(sqls.len(), 2);
|
||||
assert!(sqls[0].contains("LIMIT 2"));
|
||||
assert!(sqls[1].contains("OFFSET 2"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn csv_export_uses_the_identifier_function_the_neo4j_server_understands() {
|
||||
for (server_version, identifier) in [("Neo4j/4.4.44", "id(n)"), ("Neo4j/5.26.0", "elementId(n)")] {
|
||||
let mut pages = vec![page(&[1], None, false)].into_iter();
|
||||
let mut sqls = Vec::new();
|
||||
let options = export_options(2);
|
||||
write_table_csv_pages(
|
||||
&mut Vec::new(),
|
||||
DatabaseType::Neo4j,
|
||||
Some(server_version),
|
||||
&options,
|
||||
None,
|
||||
|sql, _| {
|
||||
sqls.push(sql);
|
||||
std::future::ready(Ok(pages.next().expect("unexpected extra export page")))
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(sqls.len(), 1);
|
||||
assert!(sqls[0].contains(identifier), "{server_version} export SQL was {}", sqls[0]);
|
||||
}
|
||||
}
|
||||
|
||||
fn salesforce_config(id: &str) -> ConnectionConfig {
|
||||
let mut config = serde_json::from_value::<ConnectionConfig>(serde_json::json!({
|
||||
"id": id,
|
||||
|
||||
@@ -55,6 +55,7 @@ fn mysql_cross_database_query_flow_preserves_target_database() {
|
||||
let save = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
|
||||
@@ -244,6 +244,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Iotdb),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
|
||||
@@ -2,9 +2,11 @@ use serde_json::Value;
|
||||
|
||||
use super::*;
|
||||
use crate::models::connection::DatabaseType;
|
||||
use crate::sql_dialect::{neo4j_element_id_function, NEO4J_LEGACY_ELEMENT_ID_FUNCTION};
|
||||
|
||||
pub(super) fn build_neo4j_data_grid_save_statements(options: &DataGridSaveStatementOptions) -> Vec<String> {
|
||||
let label = quote_ident(Some(DatabaseType::Neo4j), &options.table_meta.table_name);
|
||||
let element_id_function = neo4j_element_id_function(options.server_version.as_deref());
|
||||
let mut statements = Vec::new();
|
||||
|
||||
for (row_index, changes) in &options.dirty_rows {
|
||||
@@ -29,8 +31,10 @@ pub(super) fn build_neo4j_data_grid_save_statements(options: &DataGridSaveStatem
|
||||
if sets.is_empty() {
|
||||
continue;
|
||||
}
|
||||
statements
|
||||
.push(format!("MATCH (n:{label}) WHERE {} SET {sets};", neo4j_element_id_predicate(&options.columns, row)));
|
||||
statements.push(format!(
|
||||
"MATCH (n:{label}) WHERE {} SET {sets};",
|
||||
neo4j_element_id_predicate(&options.columns, row, element_id_function)
|
||||
));
|
||||
}
|
||||
|
||||
for row_index in &options.deleted_rows {
|
||||
@@ -39,7 +43,7 @@ pub(super) fn build_neo4j_data_grid_save_statements(options: &DataGridSaveStatem
|
||||
};
|
||||
statements.push(format!(
|
||||
"MATCH (n:{label}) WHERE {} DETACH DELETE n;",
|
||||
neo4j_element_id_predicate(&options.columns, row)
|
||||
neo4j_element_id_predicate(&options.columns, row, element_id_function)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -74,6 +78,7 @@ pub(super) fn build_neo4j_data_grid_save_statements(options: &DataGridSaveStatem
|
||||
|
||||
pub(super) fn build_neo4j_data_grid_rollback_statements(options: &DataGridSaveStatementOptions) -> Vec<String> {
|
||||
let label = quote_ident(Some(DatabaseType::Neo4j), &options.table_meta.table_name);
|
||||
let element_id_function = neo4j_element_id_function(options.server_version.as_deref());
|
||||
let mut statements = Vec::new();
|
||||
|
||||
for row in &options.new_rows {
|
||||
@@ -140,19 +145,41 @@ pub(super) fn build_neo4j_data_grid_rollback_statements(options: &DataGridSaveSt
|
||||
if sets.is_empty() {
|
||||
continue;
|
||||
}
|
||||
statements
|
||||
.push(format!("MATCH (n:{label}) WHERE {} SET {sets};", neo4j_element_id_predicate(&options.columns, row)));
|
||||
statements.push(format!(
|
||||
"MATCH (n:{label}) WHERE {} SET {sets};",
|
||||
neo4j_element_id_predicate(&options.columns, row, element_id_function)
|
||||
));
|
||||
}
|
||||
|
||||
statements
|
||||
}
|
||||
|
||||
fn neo4j_element_id_predicate(columns: &[String], row: &[Value]) -> String {
|
||||
fn neo4j_element_id_predicate(columns: &[String], row: &[Value], element_id_function: &str) -> String {
|
||||
let index = columns.iter().position(|column| column == DBX_NEO4J_ELEMENT_ID_COLUMN).unwrap_or(usize::MAX);
|
||||
format!(
|
||||
"elementId(n) = {}",
|
||||
format_grid_sql_literal(row.get(index).unwrap_or(&Value::Null), Some(DatabaseType::Neo4j), None)
|
||||
)
|
||||
let value = row.get(index).unwrap_or(&Value::Null);
|
||||
// The legacy `id()` returns an Integer while a grid cell always carries text. Comparing the two
|
||||
// with a quoted literal matches nothing, so a user edit would quietly update zero rows; the
|
||||
// identity has to be written as a number there. `elementId()` keeps its string form.
|
||||
if element_id_function == NEO4J_LEGACY_ELEMENT_ID_FUNCTION {
|
||||
if let Some(identity) = neo4j_legacy_element_id_literal(value) {
|
||||
return format!("{element_id_function}(n) = {identity}");
|
||||
}
|
||||
}
|
||||
format!("{element_id_function}(n) = {}", format_grid_sql_literal(value, Some(DatabaseType::Neo4j), None))
|
||||
}
|
||||
|
||||
/// Renders a legacy `id()` value as a Cypher number so it matches the server's Integer. Anything
|
||||
/// that is not an integral id is left to the caller's normal literal formatting.
|
||||
fn neo4j_legacy_element_id_literal(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::Number(number) => number.as_i64().map(|identity| identity.to_string()),
|
||||
Value::String(text) => {
|
||||
let trimmed = text.trim();
|
||||
(!trimmed.is_empty() && trimmed.chars().all(|character| character.is_ascii_digit()))
|
||||
.then(|| trimmed.to_string())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn neo4j_row_property_where(columns: &[String], row: &[Value]) -> String {
|
||||
|
||||
@@ -196,6 +196,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Salesforce),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
database: None,
|
||||
catalog: None,
|
||||
@@ -234,6 +235,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Salesforce),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
database: None,
|
||||
catalog: None,
|
||||
|
||||
@@ -98,6 +98,11 @@ pub struct DataGridSaveStatementOptions {
|
||||
pub database_type: Option<DatabaseType>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub identifier_quote: Option<String>,
|
||||
/// Server version reported by the connection (for example `Neo4j/4.4.44`). Neo4j renamed the
|
||||
/// node identity function in 5.0, so the saved statements must address rows the same way the
|
||||
/// grid read them.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server_version: Option<String>,
|
||||
pub table_meta: DataGridTableMeta,
|
||||
pub columns: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -3939,6 +3944,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -4174,11 +4180,87 @@ mod tests {
|
||||
assert_eq!(build_data_grid_conditional_update_sql(primary_key), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neo4j_data_grid_save_addresses_nodes_with_the_identity_function_of_the_server() {
|
||||
let options = |server_version: Option<&str>, rows: Vec<Vec<Value>>| DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Neo4j),
|
||||
identifier_quote: None,
|
||||
server_version: server_version.map(str::to_string),
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
schema: None,
|
||||
table_name: "Employee".to_string(),
|
||||
primary_keys: vec![],
|
||||
columns: Some(vec![
|
||||
column(DBX_NEO4J_ELEMENT_ID_COLUMN, "string", false, None),
|
||||
column("name", "string", true, None),
|
||||
]),
|
||||
},
|
||||
columns: vec![DBX_NEO4J_ELEMENT_ID_COLUMN.to_string(), "name".to_string()],
|
||||
source_columns: None,
|
||||
rows,
|
||||
dirty_rows: vec![(0, vec![(1, json!("after"))])],
|
||||
deleted_rows: vec![1],
|
||||
new_rows: vec![],
|
||||
include_database_name: false,
|
||||
};
|
||||
let legacy_rows = || vec![vec![json!(23), json!("before")], vec![json!(24), json!("gone")]];
|
||||
|
||||
// Neo4j 4.x only knows `id()`; `elementId()` would be an unknown function there (#10503).
|
||||
// That `id()` is an Integer while grids carry text, so the predicate compares numbers —
|
||||
// with a quoted literal the edit would silently match no row.
|
||||
for rows in [legacy_rows(), vec![vec![json!("23"), json!("before")], vec![json!("24"), json!("gone")]]] {
|
||||
let on_neo4j_4 = prepare_data_grid_save(options(Some("Neo4j/4.4.44"), rows));
|
||||
assert_eq!(
|
||||
on_neo4j_4.statements,
|
||||
vec![
|
||||
"MATCH (n:`Employee`) WHERE id(n) = 23 SET n.`name` = 'after';".to_string(),
|
||||
"MATCH (n:`Employee`) WHERE id(n) = 24 DETACH DELETE n;".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Neo4j 5.0+ renamed the identity function, and `elementId()` returns a string.
|
||||
let on_neo4j_5 = prepare_data_grid_save(options(
|
||||
Some("Neo4j/5.26.0"),
|
||||
vec![vec![json!("4:0db5d0e9:1:6"), json!("before")], vec![json!("4:0db5d0e9:1:7"), json!("gone")]],
|
||||
));
|
||||
assert_eq!(
|
||||
on_neo4j_5.statements,
|
||||
vec![
|
||||
"MATCH (n:`Employee`) WHERE elementId(n) = '4:0db5d0e9:1:6' SET n.`name` = 'after';".to_string(),
|
||||
"MATCH (n:`Employee`) WHERE elementId(n) = '4:0db5d0e9:1:7' DETACH DELETE n;".to_string(),
|
||||
]
|
||||
);
|
||||
|
||||
// A non-numeric identity never becomes a bare Cypher number, even on the legacy function.
|
||||
let legacy_text_id = prepare_data_grid_save(options(
|
||||
Some("Neo4j/4.4.44"),
|
||||
vec![vec![json!("4:0db5d0e9:1:6"), json!("before")], vec![json!("4:0db5d0e9:1:7"), json!("gone")]],
|
||||
));
|
||||
assert_eq!(
|
||||
legacy_text_id.statements,
|
||||
vec![
|
||||
"MATCH (n:`Employee`) WHERE id(n) = '4:0db5d0e9:1:6' SET n.`name` = 'after';".to_string(),
|
||||
"MATCH (n:`Employee`) WHERE id(n) = '4:0db5d0e9:1:7' DETACH DELETE n;".to_string(),
|
||||
]
|
||||
);
|
||||
|
||||
// Callers without connection metadata keep the historical `elementId()` spelling.
|
||||
let without_version = prepare_data_grid_save(options(
|
||||
None,
|
||||
vec![vec![json!("4:0db5d0e9:1:6"), json!("before")], vec![json!("4:0db5d0e9:1:7"), json!("gone")]],
|
||||
));
|
||||
assert_eq!(without_version.statements, on_neo4j_5.statements);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iris_cache_data_grid_save_uses_unquoted_ordinary_identifiers() {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Iris),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -4216,6 +4298,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Iris),
|
||||
identifier_quote: Some("\"".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -4259,6 +4342,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -4296,6 +4380,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6363,6 +6448,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Hive),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6403,6 +6489,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Hive),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6442,6 +6529,7 @@ mod tests {
|
||||
let hive_options = DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Hive),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6464,6 +6552,7 @@ mod tests {
|
||||
let postgres_options = DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6647,6 +6736,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: Some("\"".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6714,6 +6804,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: Some("\"".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6749,6 +6840,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::SqlServer),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6775,6 +6867,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::SqlServer),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: Some("BarDB".to_string()),
|
||||
database: Some("BarDB".to_string()),
|
||||
@@ -6805,6 +6898,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6843,6 +6937,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6878,6 +6973,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6913,6 +7009,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -6943,6 +7040,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7020,6 +7118,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Vastbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7051,6 +7150,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Vastbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7094,6 +7194,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Goldendb),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7126,6 +7227,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Goldendb),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7166,6 +7268,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Goldendb),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7193,6 +7296,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Goldendb),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7220,6 +7324,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7256,6 +7361,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7287,6 +7393,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: Some("`".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7320,6 +7427,7 @@ mod tests {
|
||||
let googlesql = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Spanner),
|
||||
identifier_quote: Some("`".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7344,6 +7452,7 @@ mod tests {
|
||||
let postgres_dialect = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Spanner),
|
||||
identifier_quote: Some("\"".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7375,6 +7484,7 @@ mod tests {
|
||||
let no_reported_quote = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Spanner),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7453,6 +7563,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Vastbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: Some("smes_dev".to_string()),
|
||||
@@ -7487,6 +7598,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Informix),
|
||||
identifier_quote: Some(String::new()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: Some("webcenter".to_string()),
|
||||
@@ -7531,6 +7643,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Informix),
|
||||
identifier_quote: Some(String::new()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: Some("dbx_test".to_string()),
|
||||
@@ -7566,6 +7679,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Gaussdb),
|
||||
identifier_quote: Some("\"".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7592,6 +7706,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: Some("`".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7621,6 +7736,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7659,6 +7775,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7695,6 +7812,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7729,6 +7847,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7763,6 +7882,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7804,6 +7924,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::OceanbaseOracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7851,6 +7972,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::OceanbaseOracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7882,6 +8004,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::OceanbaseOracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -7915,6 +8038,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Dameng),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8054,6 +8178,7 @@ mod tests {
|
||||
DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Sqlite),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8213,6 +8338,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::OceanbaseOracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8365,6 +8491,7 @@ mod tests {
|
||||
let options = DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8420,6 +8547,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8516,6 +8644,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: Some("`".to_string()),
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8608,6 +8737,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Dameng),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8644,6 +8774,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8694,6 +8825,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::SqlServer),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8762,6 +8894,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::SqlServer),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8796,6 +8929,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8828,6 +8962,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8875,6 +9010,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8905,6 +9041,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8935,6 +9072,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::SqlServer),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -8967,6 +9105,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9000,6 +9139,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9042,6 +9182,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9075,6 +9216,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9112,6 +9254,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9152,6 +9295,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9199,6 +9343,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9244,6 +9389,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9274,6 +9420,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9303,6 +9450,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9337,6 +9485,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Tdengine),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9368,6 +9517,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Databend),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9402,6 +9552,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::ClickHouse),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9438,6 +9589,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::ClickHouse),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9472,6 +9624,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::ClickHouse),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9502,6 +9655,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::ClickHouse),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9601,6 +9755,7 @@ mod tests {
|
||||
let save = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Doris),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta,
|
||||
columns: vec!["id".to_string(), "status".to_string()],
|
||||
source_columns: None,
|
||||
@@ -9626,6 +9781,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Databend),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9657,6 +9813,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oscar),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9688,6 +9845,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9724,6 +9882,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9778,6 +9937,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9806,6 +9966,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9840,6 +10001,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::ManticoreSearch),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9873,6 +10035,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Postgres),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9913,6 +10076,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Oracle),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9949,6 +10113,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Xugu),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -9986,6 +10151,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Sqlite),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10015,6 +10181,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Sqlite),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10047,6 +10214,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10076,6 +10244,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10107,6 +10276,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10137,6 +10307,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10168,6 +10339,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10197,6 +10369,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Kingbase),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10226,6 +10399,7 @@ mod tests {
|
||||
let result = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Sqlite),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: DataGridTableMeta {
|
||||
catalog: None,
|
||||
database: None,
|
||||
@@ -10266,6 +10440,7 @@ mod tests {
|
||||
let save = prepare_data_grid_save(DataGridSaveStatementOptions {
|
||||
database_type: Some(DatabaseType::Doris),
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
table_meta: table_meta.clone(),
|
||||
columns: vec!["name".to_string(), "v2".to_string()],
|
||||
source_columns: None,
|
||||
|
||||
@@ -47,8 +47,9 @@ pub use table_select::{
|
||||
build_table_select_sql, DBX_LARGE_VALUE_BYTES_COLUMN_PREFIX,
|
||||
};
|
||||
pub use table_select::{
|
||||
database_qualified_table_name, quote_table_data_identifier, table_data_qualified_table_name, table_data_schema,
|
||||
uses_connection_identifier_quote,
|
||||
database_qualified_table_name, neo4j_element_id_function, quote_table_data_identifier,
|
||||
table_data_qualified_table_name, table_data_schema, uses_connection_identifier_quote,
|
||||
NEO4J_LEGACY_ELEMENT_ID_FUNCTION,
|
||||
};
|
||||
pub use type_rewrite::{
|
||||
apply_auto_inc_to_column_def, column_is_auto_increment, normalize_len_params, rewrite_column_type,
|
||||
|
||||
@@ -873,6 +873,38 @@ pub(super) fn build_db2_table_select_page_sql(
|
||||
)
|
||||
}
|
||||
|
||||
/// Neo4j 5.0 replaced `id()` with `elementId()`, and servers before that only know `id()`, so the
|
||||
/// generated Cypher has to pick the spelling the connected server accepts. An unknown version keeps
|
||||
/// `elementId()`, which is what every caller without connection metadata got before.
|
||||
pub fn neo4j_element_id_function(server_version: Option<&str>) -> &'static str {
|
||||
match neo4j_major_version(server_version) {
|
||||
Some(major) if major < NEO4J_ELEMENT_ID_MIN_MAJOR_VERSION => NEO4J_LEGACY_ELEMENT_ID_FUNCTION,
|
||||
_ => "elementId",
|
||||
}
|
||||
}
|
||||
|
||||
/// The identity function Neo4j used before 5.0. Unlike `elementId()`, which returns a string, it
|
||||
/// returns the node's internal `Integer` id, so callers comparing a value read from a grid have to
|
||||
/// compare numbers.
|
||||
pub const NEO4J_LEGACY_ELEMENT_ID_FUNCTION: &str = "id";
|
||||
|
||||
/// Extracts the leading `major` from version strings such as `4.4.44`, `Neo4j/5.26.0` or
|
||||
/// `Neo4j/2025.01.0`. The product name itself contains a digit ("Neo4j"), so only a token that
|
||||
/// *starts* with digits counts as the version. Anything without one is treated as unknown.
|
||||
fn neo4j_major_version(server_version: Option<&str>) -> Option<u32> {
|
||||
let version = server_version?.trim();
|
||||
version.split(|character: char| !character.is_ascii_alphanumeric()).find_map(|token| {
|
||||
let digits: String = token.chars().take_while(char::is_ascii_digit).collect();
|
||||
if digits.is_empty() {
|
||||
None
|
||||
} else {
|
||||
digits.parse().ok()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const NEO4J_ELEMENT_ID_MIN_MAJOR_VERSION: u32 = 5;
|
||||
|
||||
pub(super) fn build_neo4j_table_select_sql(options: &TableDataSelectSqlOptions, limit: usize) -> String {
|
||||
let label = quote_table_identifier(Some(DatabaseType::Neo4j), &options.table_name);
|
||||
let predicate = normalize_where_input(options.where_input.as_deref());
|
||||
@@ -891,7 +923,8 @@ pub(super) fn build_neo4j_table_select_sql(options: &TableDataSelectSqlOptions,
|
||||
.join(", ")
|
||||
};
|
||||
let returns = format!(
|
||||
"elementId(n) AS {}, {returned_columns}",
|
||||
"{}(n) AS {}, {returned_columns}",
|
||||
neo4j_element_id_function(options.server_version.as_deref()),
|
||||
quote_table_identifier(Some(DatabaseType::Neo4j), DBX_NEO4J_ELEMENT_ID_COLUMN)
|
||||
);
|
||||
let order_by = options.order_by.as_deref().filter(|order| !order.trim().is_empty());
|
||||
@@ -979,6 +1012,7 @@ mod tests {
|
||||
database_type: Some(database_type),
|
||||
driver_profile: None,
|
||||
identifier_quote: None,
|
||||
server_version: None,
|
||||
schema: None,
|
||||
table_name: table.to_string(),
|
||||
catalog: catalog.map(|c| c.to_string()),
|
||||
|
||||
@@ -1575,6 +1575,37 @@ fn builds_oracle_and_neo4j_table_data_queries() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neo4j_element_id_function_follows_the_connected_server_version() {
|
||||
// Neo4j 4.x and older only know `id()`; `elementId()` arrived in 5.0.
|
||||
assert_eq!(neo4j_element_id_function(Some("4.4.44")), "id");
|
||||
assert_eq!(neo4j_element_id_function(Some("Neo4j/4.4.44")), "id");
|
||||
assert_eq!(neo4j_element_id_function(Some("Neo4j 4.4.44 (community)")), "id");
|
||||
assert_eq!(neo4j_element_id_function(Some("3.5.35")), "id");
|
||||
assert_eq!(neo4j_element_id_function(Some("Neo4j/5.26.0")), "elementId");
|
||||
assert_eq!(neo4j_element_id_function(Some("Neo4j/2025.01.0")), "elementId");
|
||||
// Unknown or unparsable versions keep the current spelling.
|
||||
assert_eq!(neo4j_element_id_function(None), "elementId");
|
||||
assert_eq!(neo4j_element_id_function(Some("")), "elementId");
|
||||
assert_eq!(neo4j_element_id_function(Some("Neo4j Community")), "elementId");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_neo4j_table_select_with_the_legacy_element_id_on_neo4j_4() {
|
||||
assert_eq!(
|
||||
build_table_data_select_sql(TableDataSelectSqlOptions {
|
||||
database_type: Some(DatabaseType::Neo4j),
|
||||
table_name: "Employee".to_string(),
|
||||
primary_keys: vec!["id".to_string()],
|
||||
columns: vec!["id".to_string(), "role".to_string()],
|
||||
server_version: Some("Neo4j/4.4.44".to_string()),
|
||||
limit: Some(100),
|
||||
..Default::default()
|
||||
}),
|
||||
"MATCH (n:`Employee`) RETURN id(n) AS `__DBX_ELEMENT_ID`, n.`id` AS `id`, n.`role` AS `role` LIMIT 100;"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oracle_unknown_table_type_does_not_assume_rowid_support() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -25,6 +25,11 @@ pub struct TableDataSelectSqlOptions {
|
||||
pub driver_profile: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub identifier_quote: Option<String>,
|
||||
/// Server version reported by the connection (for example `Neo4j/4.4.44`).
|
||||
/// Engines that renamed a built-in function across releases use it to emit
|
||||
/// SQL the connected server understands instead of the newest spelling.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server_version: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub schema: Option<String>,
|
||||
pub table_name: String,
|
||||
|
||||
Reference in New Issue
Block a user