1use std::sync::{Arc, LazyLock};
23
24use arrow_arith::boolean::{and, or};
25use arrow_array::builder::{BinaryBuilder, StringBuilder};
26use arrow_array::{ArrayRef, RecordBatch, StringArray};
27use arrow_ord::cmp::eq;
28use arrow_schema::{DataType, Field, Schema, SchemaRef};
29use arrow_select::{filter::filter_record_batch, take::take};
30use arrow_string::like::like;
31
32use super::lexsort_to_indices;
33use crate::error::*;
34use crate::sql::CommandGetTables;
35use crate::{IpcMessage, IpcWriteOptions, SchemaAsIpc};
36
37pub struct GetTablesBuilder {
48 catalog_filter: Option<String>,
49 table_types_filter: Vec<String>,
50 db_schema_filter_pattern: Option<String>,
52 table_name_filter_pattern: Option<String>,
54 catalog_name: StringBuilder,
56 db_schema_name: StringBuilder,
58 table_name: StringBuilder,
60 table_type: StringBuilder,
62 table_schema: Option<BinaryBuilder>,
64}
65
66impl CommandGetTables {
67 pub fn into_builder(self) -> GetTablesBuilder {
69 self.into()
70 }
71}
72
73impl From<CommandGetTables> for GetTablesBuilder {
74 fn from(value: CommandGetTables) -> Self {
75 Self::new(
76 value.catalog,
77 value.db_schema_filter_pattern,
78 value.table_name_filter_pattern,
79 value.table_types,
80 value.include_schema,
81 )
82 }
83}
84
85impl GetTablesBuilder {
86 pub fn new(
109 catalog: Option<impl Into<String>>,
110 db_schema_filter_pattern: Option<impl Into<String>>,
111 table_name_filter_pattern: Option<impl Into<String>>,
112 table_types: impl IntoIterator<Item = impl Into<String>>,
113 include_schema: bool,
114 ) -> Self {
115 let table_schema = if include_schema {
116 Some(BinaryBuilder::new())
117 } else {
118 None
119 };
120 Self {
121 catalog_filter: catalog.map(|s| s.into()),
122 table_types_filter: table_types.into_iter().map(|tt| tt.into()).collect(),
123 db_schema_filter_pattern: db_schema_filter_pattern.map(|s| s.into()),
124 table_name_filter_pattern: table_name_filter_pattern.map(|t| t.into()),
125 catalog_name: StringBuilder::new(),
126 db_schema_name: StringBuilder::new(),
127 table_name: StringBuilder::new(),
128 table_type: StringBuilder::new(),
129 table_schema,
130 }
131 }
132
133 pub fn append(
135 &mut self,
136 catalog_name: impl AsRef<str>,
137 schema_name: impl AsRef<str>,
138 table_name: impl AsRef<str>,
139 table_type: impl AsRef<str>,
140 table_schema: &Schema,
141 ) -> Result<()> {
142 self.catalog_name.append_value(catalog_name);
143 self.db_schema_name.append_value(schema_name);
144 self.table_name.append_value(table_name);
145 self.table_type.append_value(table_type);
146 if let Some(self_table_schema) = self.table_schema.as_mut() {
147 let options = IpcWriteOptions::default();
148 let message: std::result::Result<IpcMessage, _> =
150 SchemaAsIpc::new(table_schema, &options).try_into();
151 let IpcMessage(schema) = message?;
152 self_table_schema.append_value(schema);
153 }
154
155 Ok(())
156 }
157
158 pub fn build(self) -> Result<RecordBatch> {
160 let schema = self.schema();
161 let Self {
162 catalog_filter,
163 table_types_filter,
164 db_schema_filter_pattern,
165 table_name_filter_pattern,
166
167 mut catalog_name,
168 mut db_schema_name,
169 mut table_name,
170 mut table_type,
171 table_schema,
172 } = self;
173
174 let catalog_name = catalog_name.finish();
176 let db_schema_name = db_schema_name.finish();
177 let table_name = table_name.finish();
178 let table_type = table_type.finish();
179 let table_schema = table_schema.map(|mut table_schema| table_schema.finish());
180
181 let mut filters = vec![];
184
185 if let Some(catalog_filter_name) = catalog_filter {
186 let scalar = StringArray::new_scalar(catalog_filter_name);
187 filters.push(eq(&catalog_name, &scalar)?);
188 }
189
190 let tt_filter = table_types_filter
191 .into_iter()
192 .map(|tt| eq(&table_type, &StringArray::new_scalar(tt)))
193 .collect::<std::result::Result<Vec<_>, _>>()?
194 .into_iter()
195 .reduce(|filter, arr| or(&filter, &arr).unwrap());
197 if let Some(filter) = tt_filter {
198 filters.push(filter);
199 }
200
201 if let Some(db_schema_filter_pattern) = db_schema_filter_pattern {
202 let scalar = StringArray::new_scalar(db_schema_filter_pattern);
204 filters.push(like(&db_schema_name, &scalar)?)
205 }
206
207 if let Some(table_name_filter_pattern) = table_name_filter_pattern {
208 let scalar = StringArray::new_scalar(table_name_filter_pattern);
210 filters.push(like(&table_name, &scalar)?)
211 }
212
213 let batch = if let Some(table_schema) = table_schema {
214 RecordBatch::try_new(
215 schema,
216 vec![
217 Arc::new(catalog_name) as ArrayRef,
218 Arc::new(db_schema_name) as ArrayRef,
219 Arc::new(table_name) as ArrayRef,
220 Arc::new(table_type) as ArrayRef,
221 Arc::new(table_schema) as ArrayRef,
222 ],
223 )
224 } else {
225 RecordBatch::try_new(
226 schema,
228 vec![
229 Arc::new(catalog_name) as ArrayRef,
230 Arc::new(db_schema_name) as ArrayRef,
231 Arc::new(table_name) as ArrayRef,
232 Arc::new(table_type) as ArrayRef,
233 ],
234 )
235 }?;
236
237 let mut total_filter = None;
239 while let Some(filter) = filters.pop() {
240 let new_filter = match total_filter {
241 Some(total_filter) => and(&total_filter, &filter)?,
242 None => filter,
243 };
244 total_filter = Some(new_filter);
245 }
246
247 let filtered_batch = if let Some(total_filter) = total_filter {
249 filter_record_batch(&batch, &total_filter)?
250 } else {
251 batch
252 };
253
254 let sort_cols = filtered_batch.project(&[0, 1, 2, 3])?;
257 let indices = lexsort_to_indices(sort_cols.columns());
258 let columns = filtered_batch
259 .columns()
260 .iter()
261 .map(|c| take(c, &indices, None))
262 .collect::<std::result::Result<Vec<_>, _>>()?;
263
264 Ok(RecordBatch::try_new(filtered_batch.schema(), columns)?)
265 }
266
267 pub fn schema(&self) -> SchemaRef {
273 get_tables_schema(self.include_schema())
274 }
275
276 pub fn include_schema(&self) -> bool {
278 self.table_schema.is_some()
279 }
280}
281
282fn get_tables_schema(include_schema: bool) -> SchemaRef {
283 if include_schema {
284 Arc::clone(&GET_TABLES_SCHEMA_WITH_TABLE_SCHEMA)
285 } else {
286 Arc::clone(&GET_TABLES_SCHEMA_WITHOUT_TABLE_SCHEMA)
287 }
288}
289
290static GET_TABLES_SCHEMA_WITHOUT_TABLE_SCHEMA: LazyLock<SchemaRef> = LazyLock::new(|| {
292 Arc::new(Schema::new(vec![
293 Field::new("catalog_name", DataType::Utf8, true),
294 Field::new("db_schema_name", DataType::Utf8, true),
295 Field::new("table_name", DataType::Utf8, false),
296 Field::new("table_type", DataType::Utf8, false),
297 ]))
298});
299
300static GET_TABLES_SCHEMA_WITH_TABLE_SCHEMA: LazyLock<SchemaRef> = LazyLock::new(|| {
302 Arc::new(Schema::new(vec![
303 Field::new("catalog_name", DataType::Utf8, true),
304 Field::new("db_schema_name", DataType::Utf8, true),
305 Field::new("table_name", DataType::Utf8, false),
306 Field::new("table_type", DataType::Utf8, false),
307 Field::new("table_schema", DataType::Binary, false),
308 ]))
309});
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use arrow_array::{StringArray, UInt32Array};
315
316 fn get_ref_batch() -> RecordBatch {
317 RecordBatch::try_new(
318 get_tables_schema(false),
319 vec![
320 Arc::new(StringArray::from(vec![
321 "a_catalog",
322 "a_catalog",
323 "a_catalog",
324 "a_catalog",
325 "b_catalog",
326 "b_catalog",
327 "b_catalog",
328 "b_catalog",
329 ])) as ArrayRef,
330 Arc::new(StringArray::from(vec![
331 "a_schema", "a_schema", "b_schema", "b_schema", "a_schema", "a_schema",
332 "b_schema", "b_schema",
333 ])) as ArrayRef,
334 Arc::new(StringArray::from(vec![
335 "a_table", "b_table", "a_table", "b_table", "a_table", "a_table", "b_table",
336 "b_table",
337 ])) as ArrayRef,
338 Arc::new(StringArray::from(vec![
339 "TABLE", "TABLE", "TABLE", "TABLE", "TABLE", "VIEW", "TABLE", "VIEW",
340 ])) as ArrayRef,
341 ],
342 )
343 .unwrap()
344 }
345
346 fn get_ref_builder(
347 catalog: Option<&str>,
348 db_schema_filter_pattern: Option<&str>,
349 table_name_filter_pattern: Option<&str>,
350 table_types: Vec<&str>,
351 include_schema: bool,
352 ) -> GetTablesBuilder {
353 let dummy_schema = Schema::empty();
354 let tables = [
355 ("a_catalog", "a_schema", "a_table", "TABLE"),
356 ("a_catalog", "a_schema", "b_table", "TABLE"),
357 ("a_catalog", "b_schema", "a_table", "TABLE"),
358 ("a_catalog", "b_schema", "b_table", "TABLE"),
359 ("b_catalog", "a_schema", "a_table", "TABLE"),
360 ("b_catalog", "a_schema", "a_table", "VIEW"),
361 ("b_catalog", "b_schema", "b_table", "TABLE"),
362 ("b_catalog", "b_schema", "b_table", "VIEW"),
363 ];
364 let mut builder = GetTablesBuilder::new(
365 catalog,
366 db_schema_filter_pattern,
367 table_name_filter_pattern,
368 table_types,
369 include_schema,
370 );
371 for (catalog_name, schema_name, table_name, table_type) in tables {
372 builder
373 .append(
374 catalog_name,
375 schema_name,
376 table_name,
377 table_type,
378 &dummy_schema,
379 )
380 .unwrap();
381 }
382 builder
383 }
384
385 #[test]
386 fn test_tables_are_filtered() {
387 let ref_batch = get_ref_batch();
388
389 let builder = get_ref_builder(None, None, None, Vec::new(), false);
390 let table_batch = builder.build().unwrap();
391 assert_eq!(table_batch, ref_batch);
392
393 let builder = get_ref_builder(None, Some("a%"), Some("a%"), Vec::new(), false);
394 let table_batch = builder.build().unwrap();
395 let indices = UInt32Array::from(vec![0, 4, 5]);
396 let ref_filtered = RecordBatch::try_new(
397 get_tables_schema(false),
398 ref_batch
399 .columns()
400 .iter()
401 .map(|c| take(c, &indices, None))
402 .collect::<std::result::Result<Vec<_>, _>>()
403 .unwrap(),
404 )
405 .unwrap();
406 assert_eq!(table_batch, ref_filtered);
407
408 let builder = get_ref_builder(Some("a_catalog"), None, None, Vec::new(), false);
409 let table_batch = builder.build().unwrap();
410 let indices = UInt32Array::from(vec![0, 1, 2, 3]);
411 let ref_filtered = RecordBatch::try_new(
412 get_tables_schema(false),
413 ref_batch
414 .columns()
415 .iter()
416 .map(|c| take(c, &indices, None))
417 .collect::<std::result::Result<Vec<_>, _>>()
418 .unwrap(),
419 )
420 .unwrap();
421 assert_eq!(table_batch, ref_filtered);
422
423 let builder = get_ref_builder(None, None, None, vec!["VIEW"], false);
424 let table_batch = builder.build().unwrap();
425 let indices = UInt32Array::from(vec![5, 7]);
426 let ref_filtered = RecordBatch::try_new(
427 get_tables_schema(false),
428 ref_batch
429 .columns()
430 .iter()
431 .map(|c| take(c, &indices, None))
432 .collect::<std::result::Result<Vec<_>, _>>()
433 .unwrap(),
434 )
435 .unwrap();
436 assert_eq!(table_batch, ref_filtered);
437 }
438
439 #[test]
440 fn test_tables_are_sorted() {
441 let ref_batch = get_ref_batch();
442 let dummy_schema = Schema::empty();
443
444 let tables = [
445 ("b_catalog", "a_schema", "a_table", "TABLE"),
446 ("b_catalog", "b_schema", "b_table", "TABLE"),
447 ("b_catalog", "b_schema", "b_table", "VIEW"),
448 ("b_catalog", "a_schema", "a_table", "VIEW"),
449 ("a_catalog", "a_schema", "a_table", "TABLE"),
450 ("a_catalog", "b_schema", "a_table", "TABLE"),
451 ("a_catalog", "b_schema", "b_table", "TABLE"),
452 ("a_catalog", "a_schema", "b_table", "TABLE"),
453 ];
454 let mut builder = GetTablesBuilder::new(
455 None::<String>,
456 None::<String>,
457 None::<String>,
458 None::<String>,
459 false,
460 );
461 for (catalog_name, schema_name, table_name, table_type) in tables {
462 builder
463 .append(
464 catalog_name,
465 schema_name,
466 table_name,
467 table_type,
468 &dummy_schema,
469 )
470 .unwrap();
471 }
472 let table_batch = builder.build().unwrap();
473 assert_eq!(table_batch, ref_batch);
474 }
475}