fix(postgres): keep index operator classes in read and generation
Closes #7166
This commit is contained in:
31 files changed
+854
-51
No files matched your search
@@ -541,6 +541,7 @@ fn clickhouse_index_from_skipping_row(row: &[serde_json::Value]) -> IndexInfo {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,6 +793,7 @@ pub async fn list_indexes(client: &ChClient, database: &str, table: &str) -> Res
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ pub async fn list_indexes(client: &CloudflareD1Client, _schema: &str, table: &st
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
Ok(indexes)
|
||||
|
||||
@@ -210,6 +210,7 @@ fn index_info_from_row(row: &mysql_async::Row) -> IndexInfo {
|
||||
included_columns: None,
|
||||
comment: (!comment_parts.is_empty()).then(|| comment_parts.join(", ")),
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -904,6 +904,7 @@ fn index_info_from_model(model: IndexModel) -> IndexInfo {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3637,6 +3638,7 @@ mod tests {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
|
||||
assert_eq!(spec.name, "email_1");
|
||||
@@ -3662,6 +3664,7 @@ mod tests {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -3905,6 +3908,7 @@ mod tests {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
},
|
||||
IndexInfo {
|
||||
name: "users_email_unique".to_string(),
|
||||
@@ -3916,6 +3920,7 @@ mod tests {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
},
|
||||
IndexInfo {
|
||||
name: "users_status_idx".to_string(),
|
||||
@@ -3927,6 +3932,7 @@ mod tests {
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
},
|
||||
];
|
||||
let after = vec![before[0].clone(), before[2].clone()];
|
||||
|
||||
@@ -5775,6 +5775,7 @@ pub async fn list_indexes(pool: &MySqlPool, database: &str, table: &str) -> Resu
|
||||
included_columns: None,
|
||||
comment: get_opt_str(&row, "INDEX_COMMENT").filter(|value| !value.is_empty()),
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
index_position
|
||||
};
|
||||
|
||||
@@ -325,6 +325,7 @@ fn table_key_index(name: &str, line: &str, is_unique: bool, is_primary: bool, in
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -345,6 +346,7 @@ fn secondary_index(line: &str) -> Option<IndexInfo> {
|
||||
included_columns: None,
|
||||
comment: mysql_quoted_string_argument(after_name, "COMMENT"),
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ pub async fn list_indexes(pool: &mysql_async::Pool, schema: &str, table: &str) -
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
||||
@@ -3824,23 +3824,30 @@ async fn list_indexes_for_relations_with_sql(
|
||||
for row in &rows {
|
||||
let Ok(relid) = row.try_get::<_, i64>(0) else { continue };
|
||||
let all_cols: Vec<String> = row.try_get::<_, Vec<String>>(2).unwrap_or_default();
|
||||
let nkeyatts = row.try_get::<_, Option<i16>>(7).ok().flatten().unwrap_or(all_cols.len() as i16) as usize;
|
||||
let all_opclasses: Vec<Option<String>> = row.try_get::<_, Vec<Option<String>>>(3).unwrap_or_default();
|
||||
let nkeyatts = row.try_get::<_, Option<i16>>(8).ok().flatten().unwrap_or(all_cols.len() as i16) as usize;
|
||||
let split_at = nkeyatts.min(all_cols.len());
|
||||
let key_cols = all_cols[..split_at].to_vec();
|
||||
let key_opclasses = if all_opclasses.len() == all_cols.len() {
|
||||
all_opclasses[..split_at].to_vec()
|
||||
} else {
|
||||
vec![None; split_at]
|
||||
};
|
||||
let included = if split_at < all_cols.len() { all_cols[split_at..].to_vec() } else { vec![] };
|
||||
let all_is_expr: Vec<bool> = row.try_get::<_, Vec<bool>>(10).unwrap_or_default();
|
||||
let all_is_expr: Vec<bool> = row.try_get::<_, Vec<bool>>(11).unwrap_or_default();
|
||||
let key_is_expression =
|
||||
if all_is_expr.len() == all_cols.len() { all_is_expr[..split_at].to_vec() } else { Vec::new() };
|
||||
result.entry(relid).or_default().push(IndexInfo {
|
||||
name: pg_row_try_string(row, 1),
|
||||
columns: key_cols,
|
||||
is_unique: pg_row_try_bool(row, 3).unwrap_or(false),
|
||||
is_primary: pg_row_try_bool(row, 4).unwrap_or(false),
|
||||
filter: row.try_get::<_, Option<String>>(5).ok().flatten(),
|
||||
index_type: row.try_get::<_, Option<String>>(6).ok().flatten(),
|
||||
is_unique: pg_row_try_bool(row, 4).unwrap_or(false),
|
||||
is_primary: pg_row_try_bool(row, 5).unwrap_or(false),
|
||||
filter: row.try_get::<_, Option<String>>(6).ok().flatten(),
|
||||
index_type: row.try_get::<_, Option<String>>(7).ok().flatten(),
|
||||
included_columns: if included.is_empty() { None } else { Some(included) },
|
||||
comment: row.try_get::<_, Option<String>>(9).ok().flatten(),
|
||||
comment: row.try_get::<_, Option<String>>(10).ok().flatten(),
|
||||
key_is_expression,
|
||||
column_opclasses: key_opclasses,
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
@@ -3857,7 +3864,8 @@ fn postgres_indexes_for_relations_query_tiers() -> [&'static str; 2] {
|
||||
// line up, and result columns are read positionally.
|
||||
fn postgres_indexes_for_relations_sql() -> &'static str {
|
||||
"SELECT t.oid::bigint AS relid, i.relname AS index_name, \
|
||||
array_agg(COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, k.n::int, true)) ORDER BY k.n) AS columns, \
|
||||
array_agg(COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, k.n::int, false)) ORDER BY k.n) AS columns, \
|
||||
array_agg(CASE WHEN oc.opcdefault THEN NULL ELSE quote_ident(opcns.nspname) || '.' || quote_ident(oc.opcname) END ORDER BY k.n) AS column_opclasses, \
|
||||
(ix.indisunique AND ix.indisvalid) AS is_unique, \
|
||||
ix.indisprimary AS is_primary, \
|
||||
pg_get_expr(ix.indpred, ix.indrelid) AS filter_expr, \
|
||||
@@ -3870,8 +3878,10 @@ fn postgres_indexes_for_relations_sql() -> &'static str {
|
||||
JOIN pg_class t ON t.oid = ix.indrelid \
|
||||
JOIN pg_class i ON i.oid = ix.indexrelid \
|
||||
JOIN pg_am am ON am.oid = i.relam \
|
||||
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true \
|
||||
JOIN LATERAL unnest(ix.indkey, ix.indclass) WITH ORDINALITY AS k(attnum, class_oid, n) ON true \
|
||||
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND k.attnum > 0 \
|
||||
LEFT JOIN pg_opclass oc ON oc.oid = k.class_oid \
|
||||
LEFT JOIN pg_namespace opcns ON opcns.oid = oc.opcnamespace \
|
||||
WHERE t.oid = ANY($1::bigint[]) \
|
||||
GROUP BY t.oid, i.relname, i.oid, ix.indisunique, ix.indisvalid, ix.indisprimary, ix.indpred, ix.indrelid, am.amname, ix.indnkeyatts, ix.indkey \
|
||||
ORDER BY t.oid, i.relname"
|
||||
@@ -3882,7 +3892,7 @@ fn postgres_indexes_for_relations_sql() -> &'static str {
|
||||
fn postgres_indexes_for_relations_compat_sql() -> &'static str {
|
||||
"SELECT t.oid::bigint AS relid, i.relname AS index_name, \
|
||||
ARRAY( \
|
||||
SELECT COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, pos.n, true)) \
|
||||
SELECT COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, pos.n, false)) \
|
||||
FROM generate_series(1, array_length(string_to_array(ix.indkey::text, ' '), 1)) AS pos(n) \
|
||||
LEFT JOIN pg_attribute a \
|
||||
ON a.attrelid = t.oid \
|
||||
@@ -3890,6 +3900,19 @@ fn postgres_indexes_for_relations_compat_sql() -> &'static str {
|
||||
AND a.attnum > 0 \
|
||||
ORDER BY pos.n \
|
||||
) AS columns, \
|
||||
ARRAY( \
|
||||
SELECT CASE WHEN oc.opcdefault THEN NULL \
|
||||
ELSE oc.opcname \
|
||||
END \
|
||||
FROM generate_series(1, array_length(string_to_array(ix.indkey::text, ' '), 1)) AS pos(n) \
|
||||
LEFT JOIN pg_attribute a \
|
||||
ON a.attrelid = t.oid \
|
||||
AND a.attnum = (string_to_array(ix.indkey::text, ' '))[pos.n]::int2 \
|
||||
AND a.attnum > 0 \
|
||||
LEFT JOIN pg_opclass oc \
|
||||
ON oc.oid = (string_to_array(ix.indclass::text, ' '))[pos.n]::oid \
|
||||
ORDER BY pos.n \
|
||||
) AS column_opclasses, \
|
||||
(ix.indisunique AND ix.indisvalid) AS is_unique, \
|
||||
ix.indisprimary AS is_primary, \
|
||||
pg_get_expr(ix.indpred, ix.indrelid) AS filter_expr, \
|
||||
@@ -7335,7 +7358,8 @@ async fn execute_query_with_max_rows_inner(
|
||||
// Sibling of `postgres_indexes_for_relations_sql` (~line 3288), for a single
|
||||
// (schema, table) instead of a batch of oids — see the note there.
|
||||
const POSTGRES_INDEXES_SQL: &str = "SELECT i.relname AS index_name, \
|
||||
array_agg(COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, k.n::int, true)) ORDER BY k.n) AS columns, \
|
||||
array_agg(COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, k.n::int, false)) ORDER BY k.n) AS columns, \
|
||||
array_agg(CASE WHEN oc.opcdefault THEN NULL ELSE quote_ident(opcns.nspname) || '.' || quote_ident(oc.opcname) END ORDER BY k.n) AS column_opclasses, \
|
||||
(ix.indisunique AND ix.indisvalid) AS is_unique, \
|
||||
ix.indisprimary AS is_primary, \
|
||||
pg_get_expr(ix.indpred, ix.indrelid) AS filter_expr, \
|
||||
@@ -7349,8 +7373,10 @@ const POSTGRES_INDEXES_SQL: &str = "SELECT i.relname AS index_name, \
|
||||
JOIN pg_class i ON i.oid = ix.indexrelid \
|
||||
JOIN pg_namespace n ON n.oid = t.relnamespace \
|
||||
JOIN pg_am am ON am.oid = i.relam \
|
||||
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true \
|
||||
JOIN LATERAL unnest(ix.indkey, ix.indclass) WITH ORDINALITY AS k(attnum, class_oid, n) ON true \
|
||||
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND k.attnum > 0 \
|
||||
LEFT JOIN pg_opclass oc ON oc.oid = k.class_oid \
|
||||
LEFT JOIN pg_namespace opcns ON opcns.oid = oc.opcnamespace \
|
||||
WHERE t.oid = (CASE WHEN $1 = '' THEN quote_ident($2) ELSE quote_ident($1) || '.' || quote_ident($2) END)::regclass \
|
||||
GROUP BY i.relname, i.oid, ix.indisunique, ix.indisvalid, ix.indisprimary, ix.indpred, ix.indrelid, am.amname, ix.indnkeyatts, ix.indkey \
|
||||
ORDER BY i.relname";
|
||||
@@ -7359,7 +7385,7 @@ const POSTGRES_INDEXES_SQL: &str = "SELECT i.relname AS index_name, \
|
||||
// 3312) — see the note on `POSTGRES_INDEXES_SQL` above.
|
||||
const POSTGRES_INDEXES_COMPAT_SQL: &str = "SELECT i.relname AS index_name, \
|
||||
ARRAY( \
|
||||
SELECT COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, pos.n, true)) \
|
||||
SELECT COALESCE(a.attname, pg_get_indexdef(ix.indexrelid, pos.n, false)) \
|
||||
FROM generate_series(1, array_length(string_to_array(ix.indkey::text, ' '), 1)) AS pos(n) \
|
||||
LEFT JOIN pg_attribute a \
|
||||
ON a.attrelid = t.oid \
|
||||
@@ -7367,6 +7393,19 @@ const POSTGRES_INDEXES_COMPAT_SQL: &str = "SELECT i.relname AS index_name, \
|
||||
AND a.attnum > 0 \
|
||||
ORDER BY pos.n \
|
||||
) AS columns, \
|
||||
ARRAY( \
|
||||
SELECT CASE WHEN oc.opcdefault THEN NULL \
|
||||
ELSE oc.opcname \
|
||||
END \
|
||||
FROM generate_series(1, array_length(string_to_array(ix.indkey::text, ' '), 1)) AS pos(n) \
|
||||
LEFT JOIN pg_attribute a \
|
||||
ON a.attrelid = t.oid \
|
||||
AND a.attnum = (string_to_array(ix.indkey::text, ' '))[pos.n]::int2 \
|
||||
AND a.attnum > 0 \
|
||||
LEFT JOIN pg_opclass oc \
|
||||
ON oc.oid = (string_to_array(ix.indclass::text, ' '))[pos.n]::oid \
|
||||
ORDER BY pos.n \
|
||||
) AS column_opclasses, \
|
||||
(ix.indisunique AND ix.indisvalid) AS is_unique, \
|
||||
ix.indisprimary AS is_primary, \
|
||||
pg_get_expr(ix.indpred, ix.indrelid) AS filter_expr, \
|
||||
@@ -7465,6 +7504,16 @@ pub(crate) async fn postgres_relation_relkind(
|
||||
row.map(|row| row.try_get::<_, String>(0)).transpose().map_err(pg_error_to_string)
|
||||
}
|
||||
|
||||
/// Compute per-column operator classes from `pg_opclass` joined via
|
||||
/// `pg_index.indclass`. For each key column position, the schema-qualified
|
||||
/// opclass (`quote_ident(nspname) || '.' || quote_ident(opcname)`) is returned
|
||||
/// unless the class is the type's default (`oc.opcdefault`), so DDL regeneration
|
||||
/// resolves the opclass regardless of `search_path`. This applies to every key
|
||||
/// position, including expression keys (`a.attname IS NULL`): the per-column
|
||||
/// `pg_get_indexdef(indexrelid, colno, pretty)` call returns only the bare
|
||||
/// expression text (PostgreSQL sets `attrsOnly = (colno != 0)`, so the
|
||||
/// opclass/COLLATE/DESC block is skipped — see `ruleutils.c`), so the opclass
|
||||
/// must be read from `indclass` rather than parsed out of that text.
|
||||
async fn list_indexes_with_sql(
|
||||
client: &deadpool_postgres::Client,
|
||||
sql: &str,
|
||||
@@ -7477,25 +7526,32 @@ async fn list_indexes_with_sql(
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let all_cols: Vec<String> = row.try_get::<_, Vec<String>>(1).unwrap_or_default();
|
||||
let nkeyatts = row.try_get::<_, Option<i16>>(6).ok().flatten().unwrap_or(all_cols.len() as i16) as usize;
|
||||
let all_opclasses: Vec<Option<String>> = row.try_get::<_, Vec<Option<String>>>(2).unwrap_or_default();
|
||||
let nkeyatts = row.try_get::<_, Option<i16>>(7).ok().flatten().unwrap_or(all_cols.len() as i16) as usize;
|
||||
let split_at = nkeyatts.min(all_cols.len());
|
||||
let key_cols = all_cols[..split_at].to_vec();
|
||||
let key_opclasses = if all_opclasses.len() == all_cols.len() {
|
||||
all_opclasses[..split_at].to_vec()
|
||||
} else {
|
||||
vec![None; split_at]
|
||||
};
|
||||
let included = if split_at < all_cols.len() { all_cols[split_at..].to_vec() } else { vec![] };
|
||||
// `a.attname IS NULL` at a given key position means that key part came back from
|
||||
// pg_get_indexdef (a functional/expression key part), not from a real column (#6295).
|
||||
let all_is_expr: Vec<bool> = row.try_get::<_, Vec<bool>>(9).unwrap_or_default();
|
||||
let all_is_expr: Vec<bool> = row.try_get::<_, Vec<bool>>(10).unwrap_or_default();
|
||||
let key_is_expression =
|
||||
if all_is_expr.len() == all_cols.len() { all_is_expr[..split_at].to_vec() } else { Vec::new() };
|
||||
IndexInfo {
|
||||
name: pg_row_try_string(row, 0),
|
||||
columns: key_cols,
|
||||
is_unique: pg_row_try_bool(row, 2).unwrap_or(false),
|
||||
is_primary: pg_row_try_bool(row, 3).unwrap_or(false),
|
||||
filter: row.try_get::<_, Option<String>>(4).ok().flatten(),
|
||||
index_type: row.try_get::<_, Option<String>>(5).ok().flatten(),
|
||||
is_unique: pg_row_try_bool(row, 3).unwrap_or(false),
|
||||
is_primary: pg_row_try_bool(row, 4).unwrap_or(false),
|
||||
filter: row.try_get::<_, Option<String>>(5).ok().flatten(),
|
||||
index_type: row.try_get::<_, Option<String>>(6).ok().flatten(),
|
||||
included_columns: if included.is_empty() { None } else { Some(included) },
|
||||
comment: row.try_get::<_, Option<String>>(8).ok().flatten(),
|
||||
comment: row.try_get::<_, Option<String>>(9).ok().flatten(),
|
||||
key_is_expression,
|
||||
column_opclasses: key_opclasses,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
@@ -12316,6 +12372,26 @@ mod tests {
|
||||
assert!(POSTGRES_INDEXES_COMPAT_SQL.contains("AS key_is_expression"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_index_metadata_schema_qualifies_opclass() {
|
||||
// The LATERAL (modern) index-introspection SQL returns each non-default
|
||||
// opclass schema-qualified (`quote_ident(nspname) || '.' || quote_ident(opcname)`)
|
||||
// and joins `pg_namespace` on `opcnamespace`, so DDL regeneration resolves
|
||||
// opclasses (e.g. `gin_trgm_ops` from `pg_trgm`) regardless of `search_path`.
|
||||
// The compat (pre-LATERAL) fallback keeps bare `oc.opcname` — no regression,
|
||||
// just no schema-qualification for the rare old-PG × non-default-schema case.
|
||||
for sql in [POSTGRES_INDEXES_SQL, postgres_indexes_for_relations_sql()] {
|
||||
assert!(
|
||||
sql.contains("quote_ident(opcns.nspname) || '.' || quote_ident(oc.opcname)"),
|
||||
"expected schema-qualified opclass in {sql}"
|
||||
);
|
||||
assert!(
|
||||
sql.contains("LEFT JOIN pg_namespace opcns ON opcns.oid = oc.opcnamespace"),
|
||||
"expected opcnamespace join in {sql}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_owner_metadata_casts_relkind_to_text() {
|
||||
assert!(POSTGRES_OWNERS_SQL.contains("c.relkind::text AS relkind"));
|
||||
|
||||
@@ -177,6 +177,7 @@ pub async fn list_indexes(pool: &Pool, _schema: &str, table: &str) -> Result<Vec
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
||||
@@ -180,6 +180,7 @@ pub async fn list_indexes(client: &RqliteClient, _schema: &str, table: &str) ->
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2493,6 +2493,7 @@ pub async fn list_indexes(pool: &SqliteHandle, schema: &str, table: &str) -> Res
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
return Ok(indexes);
|
||||
@@ -2546,6 +2547,7 @@ pub async fn list_indexes(pool: &SqliteHandle, schema: &str, table: &str) -> Res
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
Ok(indexes)
|
||||
|
||||
@@ -2940,6 +2940,7 @@ pub async fn list_indexes(client: &mut SqlServerClient, schema: &str, table: &st
|
||||
},
|
||||
comment: row.get::<&str, _>(7).filter(|s: &&str| !s.is_empty()).map(|s: &str| s.to_string()),
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
||||
@@ -222,6 +222,7 @@ pub async fn list_indexes(client: &TursoClient, _schema: &str, table: &str) -> R
|
||||
included_columns: None,
|
||||
comment: None,
|
||||
key_is_expression: Vec::new(),
|
||||
column_opclasses: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user