Skip to content

Commit

Permalink
Database Visualization Support (#1582)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd authored and iamrecursion committed Mar 19, 2021
1 parent 2932819 commit b0e908e
Show file tree
Hide file tree
Showing 35 changed files with 392 additions and 62 deletions.
4 changes: 2 additions & 2 deletions distribution/std-lib/Standard/src/Base/Data/Json.enso
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ type Marshalling_Error
to_display_text : Text
to_display_text = case this of
Type_Mismatch_Error json format ->
json_text = Meta.type_to_display_text json
format_text = Meta.type_to_display_text format
json_text = Meta.get_simple_type_name json
format_text = Meta.get_simple_type_name format
"Type mismatch error: the json with type `" + json_text + "` did not match the format `" + format_text + "`."
Missing_Field_Error _ field _ ->
"Missing field in Json: the field `" + field.to_text "` was missing in the json."
Expand Down
17 changes: 14 additions & 3 deletions distribution/std-lib/Standard/src/Base/Meta.enso
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,20 @@ get_source_location : Integer -> Text
get_source_location skip_frames =
Builtins.Meta.get_source_location skip_frames+1

## Displays the type of the provided value as text.
## PRIVATE

Displays the type of the provided value as text.

Arguments:
- value: The value for which to display the type.
type_to_display_text : Any -> Text
type_to_display_text value = Builtins.Meta.type_to_display_text value
get_simple_type_name : Any -> Text
get_simple_type_name value = Builtins.Meta.get_simple_type_name value

## PRIVATE

Returns the fully qualified type name of the given value.

Arguments:
- value: the value to get the type of.
get_qualified_type_name : Any -> Text
get_qualified_type_name value = Builtins.Meta.get_qualified_type_name value
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ type Connection
ncols = metadata.getColumnCount
column_names = Vector.new ncols ix-> metadata.getColumnName ix+1
column_types = if expected_types.is_nothing.not then expected_types else
Vector.new ncols (ix -> Sql_Type <| metadata.getColumnType ix+1)
Vector.new ncols ix->
typeid = metadata.getColumnType ix+1
name = metadata.getColumnTypeName ix+1
Sql_Type typeid name
column_builders = column_types.map typ->
here.create_builder typ
go has_next = if has_next.not then Nothing else
Expand Down Expand Up @@ -137,8 +140,9 @@ type Connection
ncols = metadata.getColumnCount
resolve_column ix =
name = metadata.getColumnName ix+1
typ = metadata.getColumnType ix+1
[name, Sql_Type typ]
typeid = metadata.getColumnType ix+1
typename = metadata.getColumnTypeName ix+1
[name, Sql_Type typeid typename]
Vector.new ncols resolve_column

## PRIVATE
Expand Down
8 changes: 8 additions & 0 deletions distribution/std-lib/Standard/src/Database/Data/Column.enso
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,14 @@ type Aggregate_Column
mean name_suffix='_mean' =
here.make_aggregate this "AVG" name_suffix

## PRIVATE

Helper that returns the underlying column from before grouping.
ungrouped : Column
ungrouped =
new_ctx = this.context.set_groups []
Column this.name this.connection this.sql_type this.expression new_ctx

## PRIVATE

A helper method for creating an aggregated column by applying some
Expand Down
27 changes: 19 additions & 8 deletions distribution/std-lib/Standard/src/Database/Data/Sql.enso
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@ type Sql_Type

Arguments:
- typeid: a numerical type id, as defined in `java.sql.Types`.
type Sql_Type typeid
- name: a database-specific type name, used for pretty printing.
type Sql_Type typeid name

## The SQL representation of `Boolean` type.
boolean : Sql_Type
boolean = Sql_Type Types.BOOLEAN
boolean = Sql_Type Types.BOOLEAN "BOOLEAN"

## The SQL representation of `Integer` type.
integer : Sql_Type
integer = Sql_Type Types.INTEGER
integer = Sql_Type Types.INTEGER "INTEGER"

## The SQL type representing decimal numbers.
decimal : Sql_Type
decimal = Sql_Type Types.DECIMAL
decimal = Sql_Type Types.DECIMAL "DECIMAL"

## The SQL type representing a general numeric type.
numeric : Sql_Type
numeric = Sql_Type Types.NUMERIC
numeric = Sql_Type Types.NUMERIC "NUMERIC"

## The SQL type representing one of the suppported textual types.
varchar : Sql_Type
varchar = Sql_Type Types.VARCHAR "VARCHAR"

## PRIVATE

Expand All @@ -42,7 +47,7 @@ type Sql_Type
standard types so it may return false negatives for non-standard ones.
is_definitely_boolean : Boolean
is_definitely_boolean =
this.typeid == Types.BOOLEAN
[Types.BOOLEAN, Types.BIT].contains this.typeid

## PRIVATE

Expand All @@ -53,6 +58,12 @@ type Sql_Type
is_definitely_double =
[Types.FLOAT, Types.DOUBLE, Types.REAL].contains this.typeid

## PRIVATE
Returns True if this type represents a Text.
is_definitely_text : Boolean
is_definitely_text =
[Types.VARCHAR, Types.LONGVARCHAR, Types.NVARCHAR, Types.LONGNVARCHAR].contains this.typeid

## UNSTABLE

A fragment of a SQL query.
Expand Down Expand Up @@ -137,8 +148,8 @@ type Statement
to_json =
jsonify fragment = case fragment of
Sql_Code_Part code -> Json.from_pairs [["sql_code", code]]
Sql_Interpolation (Sql_Type typeid) obj ->
inner = Json.from_pairs [["value", obj.to_json], ["typeid", typeid]]
Sql_Interpolation typ obj ->
inner = Json.from_pairs [["value", obj], ["expected_sql_type", typ.name]]
Json.from_pairs [["sql_interpolation", inner]]
fragments = Json.Array (this.internal_fragments.map jsonify)
Json.from_pairs [["query", fragments]]
Expand Down
34 changes: 24 additions & 10 deletions distribution/std-lib/Standard/src/Database/Data/Table.enso
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,22 @@ type Table
turned_into_index.not
this.updated_context new_ctx . updated_columns new_cols

## Returns the index (or indexes) of this table, as a column (indexed by itself).
Returns `Nothing` if there is no index set.
index : Column | Vector Column | Nothing
## UNSTABLE

Returns the (possibly empty) list of indices for this table.
indices : Vector Column
indices =
this.context.meta_index.map this.make_column

## UNSTABLE

Returns the index (or indexes) of this table, as a column (indexed by itself).
Throws `No_Index_Set_Error` if there is no index set.
index : Column | Vector Column ! Materialized_Table.No_Index_Set_Error
index =
ixes = this.context.meta_index.map this.make_column
len = this.context.meta_index.length
if len == 0 then Nothing else
ixes = this.indices
len = ixes.length
if len == 0 then Error.throw Materialized_Table.No_Index_Set_Error else
if len == 1 then ixes.at 0 else ixes

## UNSTABLE
Expand Down Expand Up @@ -446,10 +455,7 @@ type Table
count_columns = cols.map c-> IR.Internal_Column c.name Sql.Sql_Type.integer (IR.Operation "COUNT" [c.expression])
count_table = this.updated_columns count_columns . to_dataframe
counts = count_table.columns.map c-> c.at 0
column_type_as_text col =
id = col.sql_type.typeid
JDBCType.valueOf id . getName
types = cols.map column_type_as_text
types = cols.map c-> c.sql_type.name
Materialized_Table.new [["Column", cols.map .name], ["Items Count", counts], ["SQL Type", types]] . set_index "Column"

## PRIVATE
Expand Down Expand Up @@ -540,6 +546,14 @@ type Aggregate_Table
make_column internal =
Aggregate_Column internal.name this.connection internal.sql_type internal.expression this.context

## PRIVATE

Helper that returns the underlying table from before grouping.
ungrouped : Table
ungrouped =
new_ctx = this.context.set_groups []
Table this.name this.connection this.internal_columns new_ctx

type Integrity_Error
## UNSTABLE

Expand Down
13 changes: 10 additions & 3 deletions distribution/std-lib/Standard/src/Table/Data/Column.enso
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,10 @@ type Column
count = this.length - this.count_missing

## Returns the index of this column, as a column (indexed by itself).
Returns `Nothing` if there is no index set.
index : Column | Nothing
Throws `No_Index_Set_Error` if there is no index set.
index : Column ! Table.No_Index_Set_Error
index = case this.java_column.getIndex.toColumn of
Nothing -> Nothing
Nothing -> Error.throw Table.No_Index_Set_Error
i -> Column i

## Returns the item contained in this column at the given index.
Expand Down Expand Up @@ -378,6 +378,13 @@ type Column
new_col = this.java_column.applyMask mask
Column new_col

## UNSTABLE

Returns a column truncated to at most `max_rows` rows.
take_start : Integer -> Column
take_start max_rows =
Column (this.java_column.slice 0 max_rows)

## Creates a new column given a name and a vector of elements.
from_vector : Text -> Vector -> Column
from_vector name items = Column (Java_Column.fromItems name items.to_array)
Expand Down
16 changes: 13 additions & 3 deletions distribution/std-lib/Standard/src/Table/Data/Table.enso
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ polyglot java import org.enso.table.operations.OrderBuilder
## An error returned when a non-existent column is being looked up.
type No_Such_Column_Error column_name

## An error returned when getting an index but no index is set for that table.
type No_Index_Set_Error

## Represents a column-oriented table data structure.
type Table
type Table java_table
Expand Down Expand Up @@ -108,10 +111,10 @@ type Table
Table (this.java_table.indexFromColumn index)

## Returns the index of this table, as a column (indexed by itself).
Returns `Nothing` if there is no index set.
index : Column.Column | Nothing
Throws `No_Index_Set_Error` if there is no index set.
index : Column.Column ! No_Index_Set_Error
index = case this.java_table.getIndex.toColumn of
Nothing -> Nothing
Nothing -> Error.throw No_Index_Set_Error
i -> Column.Column i

## Selects a subset of columns from this table by name.
Expand Down Expand Up @@ -306,6 +309,13 @@ type Table
concat other =
Table (this.java_table.concat other.java_table)

## UNSTABLE

Returns a table truncated to at most `max_rows` rows.
take_start : Integer -> Table
take_start max_rows =
Table (this.java_table.slice 0 max_rows)

## PRIVATE
comparator_to_java cmp x y = cmp x y . to_sign

Expand Down
11 changes: 8 additions & 3 deletions distribution/std-lib/Standard/src/Test.enso
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,25 @@ Error.should_equal _ = Panic.throw (Matched_On_Error this)
Decimal.should_equal that (epsilon = 0) = case this.equals that epsilon of
True -> Success
False ->
msg = this.to_text + " did not equal " + that.to_text + "."
loc = Meta.get_source_location 2
msg = this.to_text + " did not equal " + that.to_text + " (at " + loc + ")."
Panic.throw (Failure msg)

## Asserts that the given `Boolean` is `True`
Boolean.should_be_true = case this of
True -> Success
False -> Panic.throw (Failure "Expected False to be True.")
False ->
loc = Meta.get_source_location 2
Panic.throw (Failure "Expected False to be True (at "+loc+").")

## Asserts that the given `Boolean` is `True`.
Error.should_be_true = Panic.throw (Matched_On_Error this)

## Asserts that the given `Boolean` is `False`
Boolean.should_be_false = case this of
True -> Panic.throw (Failure "Expected True to be False.")
True ->
loc = Meta.get_source_location 2
Panic.throw (Failure "Expected True to be False (at "+loc+").")
False -> Success

## Asserts that the given `Boolean` is `False`
Expand Down
8 changes: 8 additions & 0 deletions distribution/std-lib/Standard/src/Visualization/Helpers.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from Standard.Base import all

## PRIVATE
recover_errors ~body =
result = Panic.recover body
result.catch err->
Json.from_pairs [["error", err.to_display_text]] . to_text

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from Standard.Base import all

import Standard.Visualization.Helpers

## PRIVATE

Prepares the query for visualization.

For each interpolation it provides its value, its actual type name, its
expected SQL type name and if it was possible to infer it, its expected Enso
typename.

Expected Enso types are inferred based on known SQL types and their mapping
to Enso types.
prepare_visualization x = Helpers.recover_errors <|
prepared = x.to_sql.prepare
code = prepared.first
interpolations = prepared.second
mapped = interpolations.map e->
value = e.first
actual_type = Meta.get_qualified_type_name value
expected_sql_type = e.second.name
expected_enso_type = here.find_expected_enso_type_for_sql e.second
Json.from_pairs [["value", value], ["actual_type", actual_type], ["expected_sql_type", expected_sql_type], ["expected_enso_type", expected_enso_type]]
dialect = x.connection.dialect.name
Json.from_pairs [["dialect", dialect], ["code", code], ["interpolations", mapped]] . to_text

## PRIVATE

Return an expected Enso type for an SQL type.

Expected Enso types are only inferred for some known SQL types. For unknown
types it will return `Nothing`.
find_expected_enso_type_for_sql sql_type =
if sql_type.is_definitely_integer then "Builtins.Main.Integer" else
if sql_type.is_definitely_double then "Builtins.Main.Decimal" else
if sql_type.is_definitely_text then "Builtins.Main.Text" else
if sql_type.is_definitely_boolean then "Builtins.Main.Boolean" else
Nothing
Loading

0 comments on commit b0e908e

Please sign in to comment.