Skip to content

Commit

Permalink
Migrate away from SQL_Type checks in favor of Value_Type. Allow diale…
Browse files Browse the repository at this point in the history
…cts to specify how columns are fetched and built when materializing a table.
  • Loading branch information
radeusgd committed Mar 28, 2023
1 parent aba369e commit 92ec558
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ type Connection
types_array = if types.is_nothing then Nothing else types.to_array
name_map = Map.from_vector [["TABLE_CAT", "Database"], ["TABLE_SCHEM", "Schema"], ["TABLE_NAME", "Name"], ["TABLE_TYPE", "Type"], ["REMARKS", "Description"], ["TYPE_CAT", "Type Database"], ["TYPE_SCHEM", "Type Schema"], ["TYPE_NAME", "Type Name"]]
self.jdbc_connection.with_metadata metadata->
table = Managed_Resource.bracket (metadata.getTables database schema name_like types_array) .close result_set_to_table
table = Managed_Resource.bracket (metadata.getTables database schema name_like types_array) .close result_set->
result_set_to_table result_set self.dialect.make_column_fetcher_for_type
renamed = table.rename_columns name_map
if all_fields then renamed else
renamed.select_columns ["Database", "Schema", "Name", "Type", "Description"]
Expand Down Expand Up @@ -171,7 +172,7 @@ type Connection
read_statement self statement column_type_suggestions=Nothing last_row_only=False =
type_overrides = self.dialect.get_type_mapping.prepare_type_overrides column_type_suggestions
self.jdbc_connection.with_prepared_statement statement stmt->
result_set_to_table stmt.executeQuery type_overrides last_row_only
result_set_to_table stmt.executeQuery self.dialect.make_column_fetcher_for_type type_overrides last_row_only

## ADVANCED

Expand Down Expand Up @@ -224,6 +225,6 @@ type Connection
pairs = db_table.internal_columns.map col->[col.name, SQL_Expression.Constant Nothing]
insert_query = self.dialect.generate_sql <| Query.Insert name pairs
insert_template = insert_query.prepare.first
self.jdbc_connection.load_table insert_template db_table table batch_size
self.jdbc_connection.load_table insert_template table batch_size

db_table
Original file line number Diff line number Diff line change
Expand Up @@ -645,10 +645,11 @@ type Column
is_blank : Boolean -> Column
is_blank self treat_nans_as_blank=False =
new_name = self.naming_helpers.function_name "is_blank" [self]
is_blank = case self.sql_type.is_definitely_text of
self_type = self.value_type
is_blank = case self_type.is_text of
True -> self.is_empty
False -> self.is_nothing
result = case treat_nans_as_blank && self.sql_type.is_definitely_double of
result = case treat_nans_as_blank && self_type.is_floating_point of
True -> is_blank || self.is_nan
False -> is_blank
result.rename new_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import project.Connection.Connection.Connection
import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
import project.Data.Table.Table
import project.Internal.Column_Fetcher.Column_Fetcher
import project.Internal.IR.From_Spec.From_Spec
import project.Internal.IR.Internal_Column.Internal_Column
import project.Internal.IR.Order_Descriptor.Order_Descriptor
Expand Down Expand Up @@ -93,6 +94,14 @@ type Dialect
get_type_mapping self =
Unimplemented.throw "This is an interface only."

## PRIVATE
Creates a `Column_Fetcher` used to fetch data from a result set and build
an in-memory column from it, based on the given column type.
make_column_fetcher_for_type : SQL_Type -> Column_Fetcher
make_column_fetcher_for_type self sql_type =
_ = sql_type
Unimplemented.throw "This is an interface only."

## PRIVATE
Checks if the given aggregate is supported.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import project.Data.Column.Column

polyglot java import java.sql.Types
polyglot java import java.sql.ResultSetMetaData

## Represents an internal SQL data-type.
type SQL_Type
Expand All @@ -26,3 +27,19 @@ type SQL_Type
## The SQL type representing a null value.
null : SQL_Type
null = SQL_Type.Value Types.NULL "NULL"


## PRIVATE
Constructs a `SQL_Type` from a `ResultSetMetaData` object.
from_metadata metadata ix =
typeid = metadata.getColumnType ix
typename = metadata.getColumnTypeName ix
precision = case metadata.getPrecision ix of
0 -> Nothing
p : Integer -> p
scale = metadata.getScale ix
nullable_id = metadata.isNullable ix
nullable = if nullable_id == ResultSetMetaData.columnNoNulls then False else
if nullable_id == ResultSetMetaData.columnNullable then True else
Nothing
SQL_Type.Value typeid typename precision scale nullable
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from Standard.Base import all

import Standard.Table.Data.Column.Column as Materialized_Column
import Standard.Table.Data.Type.Value_Type.Value_Type
import Standard.Table.Internal.Java_Exports

polyglot java import java.sql.ResultSet

type Column_Fetcher
## PRIVATE
A helper for fetching data from a result set and possibly building a
column out of it.

Arguments:
- fetch_value: A function that fetches a value from a result set.
- make_builder: A function that creates a builder for a column.
It takes an initial size as an argument. That size is only a suggestion
for initial capacity and the builder must be ready to accept more or
less rows than that.
Value (fetch_value : ResultSet -> Integer -> Any) (make_builder : Integer -> Builder)

## We could use `Storage.make_builder` here, but this builder allows us to pass
raw Truffle values around (like `long`) instead of boxing them.

I suspect this can allow the Truffle PE to compile this into tighter loop,
but so far I have no proof. If it turns out to be an unnecessary
micro-optimization, we can always switch to `Storage.make_builder`.
type Builder
## PRIVATE
Wraps an underlying builder to provide a generic interface.

Arguments:
- append: A function that appends a value to the underlying builder.
By default, it must support appending `Nothing`, unless the column was
explicitly declared as non-nullable.
- make_column: A function that creates a column from the underlying
builder. It takes the desired column name as argument.
Value (append : Any -> Nothing) (make_column : Text -> Materialized_Column)

## PRIVATE
boolean_fetcher : Column_Fetcher
boolean_fetcher =
fetch_value rs i =
b = rs.getBoolean i
if rs.wasNull then Nothing else b
make_builder _ =
java_builder = Java_Exports.make_bool_builder
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.appendBoolean v
Builder.Value append (seal_java_builder java_builder)
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
double_fetcher : Column_Fetcher
double_fetcher =
fetch_value rs i =
d = rs.getDouble i
if rs.wasNull then Nothing else d
make_builder initial_size =
java_builder = Java_Exports.make_double_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.appendDouble v
Builder.Value append (seal_java_builder java_builder)
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
long_fetcher : Column_Fetcher
long_fetcher =
fetch_value rs i =
l = rs.getLong i
if rs.wasNull then Nothing else l
make_builder initial_size =
java_builder = Java_Exports.make_long_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.appendLong v
Builder.Value append (seal_java_builder java_builder)
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
text_fetcher : Column_Fetcher
text_fetcher =
fetch_value rs i =
t = rs.getString i
if rs.wasNull then Nothing else t
make_builder initial_size =
java_builder = Java_Exports.make_string_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.append v
Builder.Value append (seal_java_builder java_builder)
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
A fallback fetcher that can be used for any type.
It will use `getObject` to get the desired value and the `InferredBuilder`
to create a Java column that will suit the values present.

It is used as a default fallback. It may not work correctly for specialized
types like dates, so a specialized fetcher should be used instead.
fallback_fetcher : Column_Fetcher
fallback_fetcher =
fetch_value rs i =
v = rs.getObject i
if rs.wasNull then Nothing else v
make_builder initial_size =
java_builder = Java_Exports.make_inferred_builder initial_size
append v =
if v.is_nothing then java_builder.appendNulls 1 else
java_builder.append v
Builder.Value append (seal_java_builder java_builder)
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
A default implementation that will assign specialized fetchers for the
Integer, Float, Char and Boolean value types and a fallback for any other
type.

This should try to be aligned with `Storage.make_builder`.
default_fetcher_for_value_type : Value_Type -> Column_Fetcher
default_fetcher_for_value_type value_type =
case value_type of
# TODO [RW] once we support varying bit-width in storages, we should specify it
Value_Type.Integer _ -> long_fetcher
Value_Type.Float _ -> double_fetcher
Value_Type.Char _ _ -> text_fetcher
Value_Type.Boolean -> boolean_fetcher
_ -> fallback_fetcher

## PRIVATE
seal_java_builder java_builder column_name =
storage = java_builder.seal
Java_Exports.make_column column_name storage
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ import project.Internal.Helpers
import project.Internal.IR.SQL_Expression.SQL_Expression

## PRIVATE
make_distinct_expression text_case_sensitivity problem_builder key_column =
# TODO we need to be able to access column Value_Type from here!
# may need to promote the Internal_Column to Column
sql_type = key_column.sql_type_reference.get
if sql_type.is_definitely_double then
make_distinct_expression text_case_sensitivity problem_builder key_column value_type =
if value_type.is_floating_point then
problem_builder.report_other_warning (Floating_Point_Equality.Error key_column.name)

expr = key_column.expression

if sql_type.is_definitely_text.not then expr else case text_case_sensitivity of
if value_type.is_text.not then expr else case text_case_sensitivity of
Case_Sensitivity.Insensitive locale ->
Helpers.assume_default_locale locale <|
SQL_Expression.Operation "FOLD_CASE" [expr]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ make_join_helpers left_table right_table left_column_mapping right_column_mappin
resolve_right = resolve_target_expression right_column_mapping

make_equals problem_builder left right =
if left.sql_type.is_definitely_double then
if left.value_type.is_floating_point then
problem_builder.report_other_warning (Floating_Point_Equality.Error left.name)
if right.sql_type.is_definitely_double then
if right.value_type.is_floating_point then
problem_builder.report_other_warning (Floating_Point_Equality.Error right.name)
SQL_Expression.Operation "==" [resolve_left left, resolve_right right]
make_equals_ignore_case _ left right locale =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import project.Data.SQL.Builder
import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
import project.Internal.Base_Generator
import project.Internal.SQL_Type_Mapping

import project.Data.Table.Table as Database_Table

Expand Down Expand Up @@ -96,7 +95,7 @@ type JDBC_Connection

resolve_column ix =
name = metadata.getColumnLabel ix+1
sql_type = SQL_Type_Mapping.from_metadata metadata ix+1
sql_type = SQL_Type.from_metadata metadata ix+1
[name, sql_type]

Vector.new metadata.getColumnCount resolve_column
Expand All @@ -105,8 +104,8 @@ type JDBC_Connection

Given an insert query template and the associated Database_Table, and a
Materialized_Table of data, load to the database.
load_table : Text -> Database_Table -> Materialized_Table -> Integer -> Nothing
load_table self insert_template db_table table batch_size =
load_table : Text -> Materialized_Table -> Integer -> Nothing
load_table self insert_template table batch_size =
self.with_connection java_connection->
default_autocommit = java_connection.getAutoCommit
java_connection.setAutoCommit False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
import project.Data.Table.Table
import project.Internal.Base_Generator
import project.Internal.Column_Fetcher.Column_Fetcher
import project.Internal.Column_Fetcher as Column_Fetcher_Module
import project.Internal.Common.Database_Distinct_Helper
import project.Internal.Common.Database_Join_Helper
import project.Internal.IR.Context.Context
Expand Down Expand Up @@ -90,7 +92,10 @@ type Postgres_Dialect
new_columns = setup.new_columns.first
column_mapping = Map.from_vector <| new_columns.map c-> [c.name, c]
new_key_columns = key_columns.map c-> column_mapping.at c.name
distinct_expressions = new_key_columns.map (Database_Distinct_Helper.make_distinct_expression case_sensitivity problem_builder)
type_mapping = self.get_type_mapping
distinct_expressions = new_key_columns.map column->
value_type = type_mapping.sql_type_to_value_type column.sql_type_reference.get
Database_Distinct_Helper.make_distinct_expression case_sensitivity problem_builder column value_type
new_context = Context.for_subquery setup.subquery . set_distinct_on distinct_expressions
table.updated_context_and_columns new_context new_columns subquery=True

Expand All @@ -113,6 +118,15 @@ type Postgres_Dialect
get_type_mapping : SQL_Type_Mapping
get_type_mapping self = Postgres_Type_Mapping

## PRIVATE
Creates a `Column_Fetcher` used to fetch data from a result set and build
an in-memory column from it, based on the given column type.
make_column_fetcher_for_type : SQL_Type -> Column_Fetcher
make_column_fetcher_for_type self sql_type =
type_mapping = self.get_type_mapping
value_type = type_mapping.sql_type_to_value_type sql_type
Column_Fetcher_Module.default_fetcher_for_value_type value_type

## PRIVATE
check_aggregate_support : Aggregate_Column -> Boolean ! Unsupported_Database_Operation
check_aggregate_support self aggregate =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
import project.Data.Table.Table
import project.Internal.Base_Generator
import project.Internal.Column_Fetcher.Column_Fetcher
import project.Internal.Column_Fetcher as Column_Fetcher_Module
import project.Internal.IR.From_Spec.From_Spec
import project.Internal.IR.Internal_Column.Internal_Column
import project.Internal.IR.SQL_Join_Kind.SQL_Join_Kind
Expand Down Expand Up @@ -92,6 +94,15 @@ type Redshift_Dialect
get_type_mapping : SQL_Type_Mapping
get_type_mapping self = Postgres_Type_Mapping

## PRIVATE
Creates a `Column_Fetcher` used to fetch data from a result set and build
an in-memory column from it, based on the given column type.
make_column_fetcher_for_type : SQL_Type -> Column_Fetcher
make_column_fetcher_for_type self sql_type =
type_mapping = self.get_type_mapping
value_type = type_mapping.sql_type_to_value_type sql_type
Column_Fetcher_Module.default_fetcher_for_value_type value_type

## PRIVATE
check_aggregate_support : Aggregate_Column -> Boolean ! Unsupported_Database_Operation
check_aggregate_support self aggregate =
Expand Down
Loading

0 comments on commit 92ec558

Please sign in to comment.