Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rust/datafusion/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -245,12 +245,18 @@ This library currently supports many SQL constructs, including
* `GROUP BY` together with one of the following aggregations: `MIN`, `MAX`, `COUNT`, `SUM`, `AVG`
* `ORDER BY` together with an expression and optional `ASC` or `DESC` and also optional `NULLS FIRST` or `NULLS LAST`


## Supported Functions

DataFusion strives to implement a subset of the [PostgreSQL SQL dialect](https://www.postgresql.org/docs/current/functions.html) where possible. We explicitly choose a single dialect to maximize interoperability with other tools and allow reuse of the PostgreSQL documents and tutorials as much as possible.

Currently, only a subset of the PosgreSQL dialect is implemented, and we will document any deviations.

## Information Schema

DataFusion supports the `TABLES` and `COLUMNS` views of the ISO SQL `information_schema` schema to list tables and columns respectively. More information can be found in the [Postgres docs](https://www.postgresql.org/docs/13/infoschema-schema.html)).


## Supported Data Types

DataFusion uses Arrow, and thus the Arrow type system, for query
Expand Down
315 changes: 290 additions & 25 deletions rust/datafusion/src/catalog/information_schema.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
use std::{any, sync::Arc};

use arrow::{
array::StringBuilder,
array::{StringBuilder, UInt64Builder},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
Expand All@@ -36,6 +36,7 @@ use super::{

const INFORMATION_SCHEMA: &str = "information_schema";
const TABLES: &str = "tables";
const COLUMNS: &str = "columns";

/// Wraps another [`CatalogProvider`] and adds a "information_schema"
/// schema that can introspect on tables in the catalog_list
Expand DownExpand Up@@ -91,51 +92,91 @@ struct InformationSchemaProvider {
catalog_list: Arc<dyn CatalogList>,
}

impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}
impl InformationSchemaProvider {
/// Construct the `information_schema.tables` virtual table
fn make_tables(&self) -> Arc<dyn TableProvider> {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string()]
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(&catalog_name, &schema_name, table_name)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, COLUMNS);
}

let mem_table = builder.build();

Arc::new(mem_table)
}

fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
// create a mem table with the names of tables
let mut builder = InformationSchemaTablesBuilder::new();
/// Construct the `information_schema.columns` virtual table
fn make_columns(&self) -> Arc<dyn TableProvider> {
let mut builder = InformationSchemaColumnsBuilder::new();

for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();

for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
builder.add_base_table(
for schema_name in catalog.schema_names() {
if schema_name != INFORMATION_SCHEMA {
let schema = catalog.schema(&schema_name).unwrap();
for table_name in schema.table_names() {
let table = schema.table(&table_name).unwrap();
for (i, field) in table.schema().fields().iter().enumerate() {
builder.add_column(
&catalog_name,
&schema_name,
table_name,
&table_name,
field.name(),
i,
field.is_nullable(),
field.data_type(),
)
}
}
}

// Add a final list for the information schema tables themselves
builder.add_system_table(&catalog_name, INFORMATION_SCHEMA, TABLES);
}
}

let mem_table = builder.build();

Arc::new(mem_table)
}
}

let mem_table = builder.build();
impl SchemaProvider for InformationSchemaProvider {
fn as_any(&self) -> &(dyn any::Any + 'static) {
self
}

fn table_names(&self) -> Vec<String> {
vec![TABLES.to_string(), COLUMNS.to_string()]
}

Some(Arc::new(mem_table))
fn table(&self, name: &str) -> Option<Arc<dyn TableProvider>> {
if name.eq_ignore_ascii_case("tables") {
Some(self.make_tables())
} else if name.eq_ignore_ascii_case("columns") {
Some(self.make_columns())
} else {
None
}
}
}

/// Builds the `information_schema.TABLE` table row by row

///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaTablesBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
Expand DownExpand Up@@ -221,3 +262,227 @@ impl InformationSchemaTablesBuilder {
MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}

/// Builds the `information_schema.COLUMNS` table row by row
///
/// Columns are based on https://www.postgresql.org/docs/current/infoschema-columns.html
struct InformationSchemaColumnsBuilder {
catalog_names: StringBuilder,
schema_names: StringBuilder,
table_names: StringBuilder,
column_names: StringBuilder,
ordinal_positions: UInt64Builder,
column_defaults: StringBuilder,
is_nullables: StringBuilder,
data_types: StringBuilder,
character_maximum_lengths: UInt64Builder,
character_octet_lengths: UInt64Builder,
numeric_precisions: UInt64Builder,
numeric_precision_radixes: UInt64Builder,
numeric_scales: UInt64Builder,
datetime_precisions: UInt64Builder,
interval_types: StringBuilder,
}

impl InformationSchemaColumnsBuilder {
fn new() -> Self {
// StringBuilder requires providing an initial capacity, so
// pick 10 here arbitrarily as this is not performance
// critical code and the number of tables is unavailable here.
let default_capacity = 10;
Self {
catalog_names: StringBuilder::new(default_capacity),
schema_names: StringBuilder::new(default_capacity),
table_names: StringBuilder::new(default_capacity),
column_names: StringBuilder::new(default_capacity),
ordinal_positions: UInt64Builder::new(default_capacity),
column_defaults: StringBuilder::new(default_capacity),
is_nullables: StringBuilder::new(default_capacity),
data_types: StringBuilder::new(default_capacity),
character_maximum_lengths: UInt64Builder::new(default_capacity),
character_octet_lengths: UInt64Builder::new(default_capacity),
numeric_precisions: UInt64Builder::new(default_capacity),
numeric_precision_radixes: UInt64Builder::new(default_capacity),
numeric_scales: UInt64Builder::new(default_capacity),
datetime_precisions: UInt64Builder::new(default_capacity),
interval_types: StringBuilder::new(default_capacity),
}
}

#[allow(clippy::too_many_arguments)]
fn add_column(
&mut self,
catalog_name: impl AsRef<str>,
schema_name: impl AsRef<str>,
table_name: impl AsRef<str>,
column_name: impl AsRef<str>,
Comment on lines 315 to 318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not it matters much, just more like an FYI: this can cause large binaries as every variation used is compiled individually.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change it into just taking &str -- though I think in the case since there is just one callsite there is likely to be just one version of the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it was really just if you had not though about it. No need to change anything imo 👍

column_position: usize,
is_nullable: bool,
data_type: &DataType,
) {
use DataType::*;

// Note: append_value is actually infallable.
self.catalog_names
.append_value(catalog_name.as_ref())
.unwrap();
self.schema_names
.append_value(schema_name.as_ref())
.unwrap();
self.table_names.append_value(table_name.as_ref()).unwrap();

self.column_names
.append_value(column_name.as_ref())
.unwrap();

self.ordinal_positions
.append_value(column_position as u64)
.unwrap();

// DataFusion does not support column default values, so null
self.column_defaults.append_null().unwrap();

// "YES if the column is possibly nullable, NO if it is known not nullable. "
let nullable_str = if is_nullable { "YES" } else { "NO" };
self.is_nullables.append_value(nullable_str).unwrap();

// "System supplied type" --> Use debug format of the datatype
self.data_types
.append_value(format!("{:?}", data_type))
.unwrap();

// "If data_type identifies a character or bit string type, the
// declared maximum length; null for all other data types or
// if no maximum length was declared."
//
// Arrow has no equivalent of VARCHAR(20), so we leave this as Null
let max_chars = None;
self.character_maximum_lengths
.append_option(max_chars)
.unwrap();

// "Maximum length, in bytes, for binary data, character data,
// or text and image data."
let char_len: Option<u64> = match data_type {
Utf8 | Binary => Some(i32::MAX as u64),
LargeBinary | LargeUtf8 => Some(i64::MAX as u64),
_ => None,
};
self.character_octet_lengths
.append_option(char_len)
.unwrap();

// numeric_precision: "If data_type identifies a numeric type, this column
// contains the (declared or implicit) precision of the type
// for this column. The precision indicates the number of
// significant digits. It can be expressed in decimal (base
// 10) or binary (base 2) terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null."
//
// numeric_radix: If data_type identifies a numeric type, this
// column indicates in which base the values in the columns
// numeric_precision and numeric_scale are expressed. The
// value is either 2 or 10. For all other data types, this
// column is null.
//
// numeric_scale: If data_type identifies an exact numeric
// type, this column contains the (declared or implicit) scale
// of the type for this column. The scale indicates the number
// of significant digits to the right of the decimal point. It
// can be expressed in decimal (base 10) or binary (base 2)
// terms, as specified in the column
// numeric_precision_radix. For all other data types, this
// column is null.
let (numeric_precision, numeric_radix, numeric_scale) = match data_type {
Int8 | UInt8 => (Some(8), Some(2), None),
Int16 | UInt16 => (Some(16), Some(2), None),
Int32 | UInt32 => (Some(32), Some(2), None),
// From max value of 65504 as explained on
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
Float16 => (Some(15), Some(2), None),
// Numbers from postgres `real` type
Float32 => (Some(24), Some(2), None),
// Numbers from postgres `double` type
Float64 => (Some(24), Some(2), None),
Decimal(precision, scale) => {
(Some(*precision as u64), Some(10), Some(*scale as u64))
}
_ => (None, None, None),
};

self.numeric_precisions
.append_option(numeric_precision)
.unwrap();
self.numeric_precision_radixes
.append_option(numeric_radix)
.unwrap();
self.numeric_scales.append_option(numeric_scale).unwrap();

self.datetime_precisions.append_option(None).unwrap();
self.interval_types.append_null().unwrap();
}

fn build(self) -> MemTable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be more idiomatic to use Into, or From.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good idea

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 98f702da90d18d8ae855a5eca6b0d1d6c1809551

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(which I now realize I put into #9866 rather than this PR 🤦 ) -- but it should get in that PR hopefully

let schema = Schema::new(vec![
Field::new("table_catalog", DataType::Utf8, false),
Field::new("table_schema", DataType::Utf8, false),
Field::new("table_name", DataType::Utf8, false),
Field::new("column_name", DataType::Utf8, false),
Field::new("ordinal_position", DataType::UInt64, false),
Field::new("column_default", DataType::Utf8, false),
Field::new("is_nullable", DataType::Utf8, false),
Field::new("data_type", DataType::Utf8, false),
Field::new("character_maximum_length", DataType::UInt64, false),
Field::new("character_octet_length", DataType::UInt64, false),
Field::new("numeric_precision", DataType::UInt64, false),
Field::new("numeric_precision_radix", DataType::UInt64, false),
Field::new("numeric_scale", DataType::UInt64, false),
Field::new("datetime_precision", DataType::UInt64, false),
Field::new("interval_type", DataType::Utf8, false),
]);

let Self {
mut catalog_names,
mut schema_names,
mut table_names,
mut column_names,
mut ordinal_positions,
mut column_defaults,
mut is_nullables,
mut data_types,
mut character_maximum_lengths,
mut character_octet_lengths,
mut numeric_precisions,
mut numeric_precision_radixes,
mut numeric_scales,
mut datetime_precisions,
mut interval_types,
} = self;

let schema = Arc::new(schema);
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(catalog_names.finish()),
Arc::new(schema_names.finish()),
Arc::new(table_names.finish()),
Arc::new(column_names.finish()),
Arc::new(ordinal_positions.finish()),
Arc::new(column_defaults.finish()),
Arc::new(is_nullables.finish()),
Arc::new(data_types.finish()),
Arc::new(character_maximum_lengths.finish()),
Arc::new(character_octet_lengths.finish()),
Arc::new(numeric_precisions.finish()),
Arc::new(numeric_precision_radixes.finish()),
Arc::new(numeric_scales.finish()),
Arc::new(datetime_precisions.finish()),
Arc::new(interval_types.finish()),
],
)
.unwrap();

MemTable::try_new(schema, vec![vec![batch]]).unwrap()
}
}
Loading