Skip to content

Commit

Permalink
Refactor database dialect types (#10437)
Browse files Browse the repository at this point in the history
* Auto-commit work in progress before clean build on 2024-07-03 14:17:22

* Refactor

* Revert

* revert

* Code Review feedback

* Green

* 2 Red

* Green

* Renames

* Code review changes

* Code review changes
  • Loading branch information
AdRiley authored Jul 5, 2024
1 parent 9449bf3 commit fc93b4d
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Standard.Database.Internal.Postgres.Postgres_Type_Mapping.Postgres_Type_M
import Standard.Database.Internal.SQL_Type_Mapping.SQL_Type_Mapping
import Standard.Database.Internal.SQL_Type_Reference.SQL_Type_Reference
import Standard.Database.Internal.Statement_Setter.Statement_Setter
import Standard.Database.SQL.SQL_Builder
import Standard.Database.SQL_Statement.SQL_Statement
import Standard.Database.SQL_Type.SQL_Type
from Standard.Database.Errors import SQL_Error, Unsupported_Database_Operation
Expand All @@ -33,7 +34,7 @@ import project.Database.Redshift.Internal.Redshift_Error_Mapper.Redshift_Error_M
The dialect for Redshift connections.
redshift : Redshift_Dialect
redshift =
Redshift_Dialect.Value Postgres_Dialect.make_internal_generator_dialect
Redshift_Dialect.Value Postgres_Dialect.make_dialect_operations

## PRIVATE

Expand All @@ -42,7 +43,7 @@ type Redshift_Dialect
## PRIVATE

The dialect for Redshift connections.
Value internal_generator_dialect
Value dialect_operations

## PRIVATE
Name of the dialect.
Expand All @@ -57,15 +58,28 @@ type Redshift_Dialect
according to the specific dialect.
generate_sql : Query -> SQL_Statement
generate_sql self query =
Base_Generator.generate_query self.internal_generator_dialect query . build
Base_Generator.generate_query self query . build

## PRIVATE
Generates SQL to truncate a table.
generate_truncate_table_sql : Text -> SQL_Builder
generate_truncate_table_sql self table_name =
Base_Generator.truncate_table_delete_from_style self table_name

## PRIVATE
Wraps and possibly escapes the identifier so that it can be used in a
generated query regardless of what characters it contains.
The quotes used will depend on the dialect.
wrap_identifier : Text -> Text
wrap_identifier : Text -> SQL_Builder
wrap_identifier self identifier =
self.internal_generator_dialect.wrap_identifier_raw identifier
Base_Generator.wrap_in_quotes identifier

## PRIVATE
Generates a SQL expression for a table literal.
make_table_literal : Vector (Vector Text) -> Vector Text -> Text -> SQL_Builder
make_table_literal self vecs column_names as_name =
Base_Generator.default_make_table_literal self.wrap_identifier vecs column_names as_name


## PRIVATE
Prepares an ordering descriptor.
Expand Down Expand Up @@ -159,7 +173,7 @@ type Redshift_Dialect
Checks if an operation is supported by the dialect.
is_supported : Text -> Boolean
is_supported self operation =
self.internal_generator_dialect.is_supported operation
self.dialect_operations.is_supported operation

## PRIVATE
The default table types to use when listing tables.
Expand Down
8 changes: 8 additions & 0 deletions distribution/lib/Standard/Database/0.0.0-dev/src/Dialect.enso
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import project.Internal.SQL_Type_Mapping.SQL_Type_Mapping
import project.Internal.SQL_Type_Reference.SQL_Type_Reference
import project.Internal.SQLite.SQLite_Dialect
import project.Internal.Statement_Setter.Statement_Setter
import project.SQL.SQL_Builder
import project.SQL_Statement.SQL_Statement
import project.SQL_Type.SQL_Type
from project.Errors import SQL_Error, Unsupported_Database_Operation
Expand All @@ -46,6 +47,13 @@ type Dialect
_ = [query]
Unimplemented.throw "This is an interface only."

## PRIVATE
Generates SQL to truncate a table.
generate_truncate_table_sql : Text -> SQL_Builder
generate_truncate_table_sql self table_name =
_ = [table_name]
Unimplemented.throw "This is an interface only."

## PRIVATE
Prepares an ordering descriptor.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ from Standard.Base import all
import Standard.Base.Errors.Illegal_State.Illegal_State

import project.Column_Constraint.Column_Constraint
import project.Dialect.Dialect
import project.Internal.IR.Context.Context
import project.Internal.IR.From_Spec.From_Spec
import project.Internal.IR.Nulls_Order.Nulls_Order
Expand All @@ -13,52 +14,37 @@ import project.SQL.SQL_Builder
from project.Errors import Unsupported_Database_Operation
from project.Internal.IR.Operation_Metadata import Row_Number_Metadata

type Internal_Dialect
type Dialect_Operations

## PRIVATE

An internal representation of a SQL dialect.
Operations supported by a particular SQL dialect and how they are implemeneted.

Arguments:
- operation_map: The mapping which maps operation names to their
implementations; each implementation is a function which takes SQL
builders for the arguments, and optionally an additional metadata
argument, and should return a SQL builder yielding code for the whole
operation.
- wrap_identifier_raw: A function that converts an arbitrary supported
identifier name in such a way that it can be used in the query; that
usually consists of wrapping the name in quotes and escaping any quotes
within it.
- make_table_literal: A function that generates a SQL expression for a
table literal.
Value (operation_map:(Map Text (Vector (SQL_Builder->SQL_Builder)))) (wrap_identifier_raw:(Text->Text)) (make_table_literal : Vector (Vector Text) -> Vector Text -> Text -> SQL_Builder)

## PRIVATE
wrap_identifier : Text -> SQL_Builder
wrap_identifier self identifier:Text =
SQL_Builder.code (self.wrap_identifier_raw identifier)
Value (operation_map:(Map Text (Vector (SQL_Builder->SQL_Builder))))

## PRIVATE

Creates a copy of the dialect that supports additional operations or
overrides existing ones.

# extend_with : Vector [Text, Vector SQL_Builder -> SQL_Builder] -> Internal_Dialect
extend_with : Vector Any -> Internal_Dialect
# extend_with : Vector [Text, Vector SQL_Builder -> SQL_Builder] -> Dialect_Operations
extend_with : Vector Any -> Dialect_Operations
extend_with self mappings =
new_map = mappings.fold self.operation_map (m -> el -> m.insert (el.at 0) (el.at 1))
Internal_Dialect.Value new_map self.wrap_identifier_raw self.make_table_literal
Dialect_Operations.Value new_map

## PRIVATE
Checks if an operation is supported by the dialect.
is_supported : Text -> Boolean
is_supported self operation =
self.operation_map.contains_key operation

## PRIVATE
override_make_table_literal self (make_table_literal : Vector (Vector Text) -> Vector Text -> Text -> SQL_Builder) -> Internal_Dialect =
Internal_Dialect.Value self.operation_map self.wrap_identifier_raw make_table_literal

## PRIVATE

A helper function to create a binary operator.
Expand Down Expand Up @@ -185,18 +171,18 @@ make_constant sql_code =

This is the simplest way of escaping identifiers that should work across most
dialects.
wrap_in_quotes : Text -> Text
wrap_in_quotes : Text -> SQL_Builder
wrap_in_quotes identifier =
escaped = identifier.replace '"' '""'
'"'+escaped+'"'
SQL_Builder.code ('"'+escaped+'"')

## PRIVATE

The base SQL dialect that is shared between most SQL implementations.

It is a base to help creating concrete dialects. It can be extended or
completely overridden.
base_dialect =
base_dialect_operations =
bin = name -> [name, make_binary_op name]
unary = name -> [name, make_unary_op name]
fun = name -> [name, make_function name]
Expand All @@ -215,7 +201,7 @@ base_dialect =
types = [simple_cast]
windows = [["ROW_NUMBER", make_row_number], ["ROW_NUMBER_IN_GROUP", make_row_number_in_group]]
base_map = Map.from_vector (arith + logic + compare + functions + agg + counts + text + nulls + contains + types + windows)
Internal_Dialect.Value base_map wrap_in_quotes (default_make_table_literal wrap_in_quotes)
Dialect_Operations.Value base_map

## PRIVATE
is_empty = lift_unary_op "IS_EMPTY" arg->
Expand Down Expand Up @@ -315,7 +301,7 @@ make_row_number_in_group arguments =
Arguments:
- dialect: The SQL dialect in which the expression is being generated.
- expr: The expression to generate SQL code for.
generate_expression : Internal_Dialect -> SQL_Expression | Order_Descriptor | Query -> SQL_Builder
generate_expression : Dialect -> SQL_Expression | Order_Descriptor | Query -> SQL_Builder
generate_expression dialect expr = case expr of
SQL_Expression.Column origin name ->
dialect.wrap_identifier origin ++ '.' ++ dialect.wrap_identifier name
Expand All @@ -325,7 +311,7 @@ generate_expression dialect expr = case expr of
escaped = value.replace "'" "''"
SQL_Builder.code ("'" + escaped + "'")
SQL_Expression.Operation kind arguments metadata ->
op = dialect.operation_map.get kind (Error.throw <| Unsupported_Database_Operation.Error kind)
op = dialect.dialect_operations.operation_map.get kind (Error.throw <| Unsupported_Database_Operation.Error kind)
parsed_args = arguments.map (generate_expression dialect)
result = op parsed_args
# If the function expects more arguments, we pass the metadata as the last argument.
Expand All @@ -343,7 +329,7 @@ generate_expression dialect expr = case expr of
Arguments:
- dialect: The dialect for which to add the alias.
- name: The name of the alias.
alias : Internal_Dialect -> Text -> SQL_Builder
alias : Dialect -> Text -> SQL_Builder
alias dialect name =
wrapped = dialect.wrap_identifier name
SQL_Builder.code " AS " ++ wrapped
Expand All @@ -355,7 +341,7 @@ alias dialect name =
Arguments:
- dialect: The SQL dialect for which the code is generated.
- from_spec: A description of the FROM clause.
generate_from_part : Internal_Dialect -> From_Spec -> SQL_Builder
generate_from_part : Dialect -> From_Spec -> SQL_Builder
generate_from_part dialect from_spec = case from_spec of
From_Spec.Table name as_name _ ->
dialect.wrap_identifier name ++ alias dialect as_name
Expand Down Expand Up @@ -384,9 +370,7 @@ generate_from_part dialect from_spec = case from_spec of
## PRIVATE
default_make_table_literal wrap_identifier vecs column_names as_name =
values = SQL_Builder.join ", " (vecs.transpose.map (vec-> SQL_Builder.join ", " (vec.map SQL_Builder.interpolation) . paren))
wrap_identifier_as_builder n =
SQL_Builder.code (wrap_identifier n)
structure = (wrap_identifier_as_builder as_name) ++ (SQL_Builder.join ", " (column_names.map wrap_identifier_as_builder) . paren)
structure = (wrap_identifier as_name) ++ (SQL_Builder.join ", " (column_names.map wrap_identifier) . paren)
SQL_Builder.code "(VALUES " ++ values ++ ") AS " ++ structure

## PRIVATE
Expand Down Expand Up @@ -420,7 +404,7 @@ make_not_equals a b =
Arguments:
- dialect: The SQL dialect for which the code is generated.
- order_descriptor: A description of the ORDER clause.
generate_order : Internal_Dialect -> Order_Descriptor -> SQL_Builder
generate_order : Dialect -> Order_Descriptor -> SQL_Builder
generate_order dialect order_descriptor =
order_suffix = case order_descriptor.direction of
Sort_Direction.Ascending -> " ASC"
Expand All @@ -443,7 +427,7 @@ generate_order dialect order_descriptor =
Arguments:
- dialect: The SQL dialect for which the code is being generated.
- ctx: A description of the SELECT clause.
generate_select_context : Internal_Dialect -> Context -> SQL_Builder
generate_select_context : Dialect -> Context -> SQL_Builder
generate_select_context dialect ctx =
gen_exprs exprs = exprs.map (generate_expression dialect)
from_part = generate_from_part dialect ctx.from_spec
Expand All @@ -466,7 +450,7 @@ generate_select_context dialect ctx =
- table_name: The name of the table into which the values are being inserted.
- pairs: The values to insert into the table, consisting of pairs of key, and
expression returning a value.
generate_insert_query : Internal_Dialect -> Text -> Vector Any -> SQL_Builder
generate_insert_query : Dialect -> Text -> Vector Any -> SQL_Builder
generate_insert_query dialect table_name pairs =
names = SQL_Builder.join ", " <| pairs.map (.first >> dialect.wrap_identifier)
values = SQL_Builder.join ", " <| pairs.map (.second >> generate_expression dialect)
Expand All @@ -481,7 +465,7 @@ generate_insert_query dialect table_name pairs =
Arguments:
- dialect: The SQL dialect for which the code is being generated.
- query: An IR describing the query.
generate_query : Internal_Dialect -> Query -> SQL_Builder
generate_query : Dialect -> Query -> SQL_Builder
generate_query dialect query = case query of
Query.Select columns ctx ->
gen_column pair = (generate_expression dialect pair.second) ++ alias dialect pair.first
Expand All @@ -504,7 +488,7 @@ generate_query dialect query = case query of
maybe_if_exists = if if_exists then SQL_Builder.code "IF EXISTS " else SQL_Builder.empty
SQL_Builder.code "DROP TABLE " ++ maybe_if_exists ++ dialect.wrap_identifier name
Query.Truncate_Table name ->
SQL_Builder.code "DELETE FROM " ++ dialect.wrap_identifier name
dialect.generate_truncate_table_sql name
Query.Insert_From_Select table_name column_names select_query -> case select_query of
Query.Select _ _ ->
inner_query = generate_query dialect select_query
Expand Down Expand Up @@ -617,3 +601,15 @@ generate_column_description dialect descriptor =
like a SQL query for the given dialect or is rather a table name.
is_probably_a_query : Text -> Boolean
is_probably_a_query text = (text.contains "SELECT ") || (text.contains "EXEC ")

## PRIVATE
option for implementing generate_truncate_table_sql
truncate_table_delete_from_style : Dialect -> Text -> SQL_Builder
truncate_table_delete_from_style dialect table_name =
SQL_Builder.code "DELETE FROM " ++ dialect.wrap_identifier table_name

## PRIVATE
option for implementing generate_truncate_table_sql
truncate_table_truncate_table_style : Dialect -> Text -> SQL_Builder
truncate_table_truncate_table_style dialect table_name =
SQL_Builder.code "TRUNCATE TABLE " ++ dialect.wrap_identifier table_name
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ polyglot java import java.sql.Types
The dialect of PostgreSQL databases.
postgres : Postgres_Dialect
postgres =
Postgres_Dialect.Value make_internal_generator_dialect
Postgres_Dialect.Value make_dialect_operations

## PRIVATE
The dialect of PostgreSQL databases.
type Postgres_Dialect
## PRIVATE
The dialect of PostgreSQL databases.
Value internal_generator_dialect
Value dialect_operations

## PRIVATE
Name of the dialect.
Expand All @@ -69,23 +69,27 @@ type Postgres_Dialect
according to the specific dialect.
generate_sql : Query -> SQL_Statement
generate_sql self query =
case query of
# Special handling to use TRUNCATE TABLE instead of the default (since being more portable) DELETE FROM.
Query.Truncate_Table name ->
wrapped_name = self.internal_generator_dialect.wrap_identifier name
# ONLY is used to avoid truncating other tables that inherit from this table, which is the default behaviour on Postgres.
(SQL_Builder.code "TRUNCATE TABLE ONLY " ++ wrapped_name) . build
_ ->
# Rely on the base generator for most queries.
Base_Generator.generate_query self.internal_generator_dialect query . build
Base_Generator.generate_query self query . build

## PRIVATE
Generates SQL to truncate a table.
generate_truncate_table_sql : Text -> SQL_Builder
generate_truncate_table_sql self table_name =
Base_Generator.truncate_table_truncate_table_style self table_name

## PRIVATE
Wraps and possibly escapes the identifier so that it can be used in a
generated query regardless of what characters it contains.
The quotes used will depend on the dialect.
wrap_identifier : Text -> Text
wrap_identifier : Text -> SQL_Builder
wrap_identifier self identifier =
self.internal_generator_dialect.wrap_identifier_raw identifier
Base_Generator.wrap_in_quotes identifier

## PRIVATE
Generates a SQL expression for a table literal.
make_table_literal : Vector (Vector Text) -> Vector Text -> Text -> SQL_Builder
make_table_literal self vecs column_names as_name =
Base_Generator.default_make_table_literal self.wrap_identifier vecs column_names as_name

## PRIVATE
Prepares an ordering descriptor.
Expand Down Expand Up @@ -242,7 +246,7 @@ type Postgres_Dialect
Checks if an operation is supported by the dialect.
is_supported : Text -> Boolean
is_supported self operation =
self.internal_generator_dialect.is_supported operation
self.dialect_operations.is_supported operation

## PRIVATE
The default table types to use when listing tables.
Expand Down Expand Up @@ -299,7 +303,7 @@ type Postgres_Dialect
_ -> base_type

## PRIVATE
make_internal_generator_dialect =
make_dialect_operations =
cases = [["LOWER", Base_Generator.make_function "LOWER"], ["UPPER", Base_Generator.make_function "UPPER"]]
text = [starts_with, contains, ends_with, agg_shortest, agg_longest, make_case_sensitive, ["REPLACE", replace], left, right]+concat_ops+cases+trim_ops
counts = [agg_count_is_null, agg_count_empty, agg_count_not_empty, ["COUNT_DISTINCT", agg_count_distinct], ["COUNT_DISTINCT_INCLUDE_NULL", agg_count_distinct_include_null]]
Expand All @@ -313,7 +317,7 @@ make_internal_generator_dialect =
special_overrides = [is_null, is_empty]
other = [["RUNTIME_ERROR", make_runtime_error_op]]
my_mappings = text + counts + stats + first_last_aggregators + arith_extensions + bool + date_ops + special_overrides + other
Base_Generator.base_dialect . extend_with my_mappings
Base_Generator.base_dialect_operations . extend_with my_mappings

## PRIVATE
This overrides the default behaviour, due to a weird behaviour of Postgres -
Expand Down
Loading

0 comments on commit fc93b4d

Please sign in to comment.