Terrazin
Idea
Simple and comfortable, as possible, data structures parser in to SQL.
Data
Describing sql with data structures like honeysql or ql in clojure.
Constructor
Construct data structures inside Constructor instance.
Result
Get result and access any returned data rails like syntax.
Realization
This is my first gem and first close meeting with OOP... I would appreciate any help =)
And sorry for my English =(
Readiness
Terrazine is not finished yet. Now it has allmost full SELECT builder, but with some limitations like:
- awfull where syntax
- bad join syntax
- not all SQL functions supported
And now it supports only Postgresql.
Detailed description
Usage
Initialization
Add this line to the Gemfile
gem 'terrazine', '0.0.2'
After server initialization set Terrazine.config
. Now config accepts only :connection
option. In the bright future will be added :adapter
option support.
In rails you can set config with after_initialize and it will looks like:
UPD: On production, rails closing PG::Connection
from after_initialize
, as fast fix connection now can be Proc
object which must return PG::Connection
. Later i'll try to find better solution
module Name
class Application < Rails::Application
config.after_initialize do
Terrazine.config connection: -> { ActiveRecord::Base.connection.raw_connection }
end
end
end
Workflow
- Describe whole data structure, or create
Constructor
instance and combine parts of data by it instance methods. - Send result to
Terrazine.send_request(structure||constructor, params = {})
- Rejoice at the
::Result
Constructor
You can create Constructor instance by calling Terrazine.new_constructor
. It optional accepts data structure.
constructor = Terrazine.new_constructor
constructor_2 = Terrazine.new_constructor from: :calls
Instance methods
Instance methods write or combine data inside constructor instance.
Not finished methods - just rewrites structure without combination with existing data.
Data Structures
You can take a look on more detailed examples in spec/constructor_spec.rb
Common patterns
SQL Function
Structure:
Array
- first element -
Symbol
that begins from _ - :_nullif
- arguments
[:_count, [:_nullif, :row, [:_params, 'mrgl']]]
Detailed Functions description.
Columns
Possible structures:
String
- if it locted in the
Hash
with table alias, table alias will be added to it - if there is no table alias it will be returned to the builder as it is.
Symbol
- just parsed to stringHash
- key - table alias||name
- value - columns
- SQL function
Array
- holder of any possible structures
['name', {u: ['role', 'u.phone, m.rating', :field]}]
Detailed Select description
Tables
Possible structures:
String
|| Symbol
- SQL function
Array
- if there is no
Array
inside it will be joined structure.join ' '
- otherwise it will be recursive mapped
['users u', [:_values, ...], [:masters, :m]]
'users u, (VALUES...), masters m'
Conditions
Not finished yet...
Column can be described as :u__name => 'u.name'
or :name
Possible structures:
String
passes as it isHash
represent sql =
or IN
if value is Array
. TODO: IS
in case of nil
or false
Symbol
- column nameArray
- only as value! will be placed in querry params ($1)
.String
- will be placed in querry params
Array
- first element -
Symbol
operator representation, by default :and
eq
- =
or
, and
in
not
like
, ilike
reg
- ~
, reg_i
- ~*
, reg_f
- !~
, reg_fi
- !~*
- arguments
Array
- holder of any possible structures
[[:not, 'z = 13'],
[:or, 'mrgl = 2', 'rgl = 22'],
[:or, 'rgl = 12', 'zgl = lol']]
[{ role: 'manager', id: [0, 1, 153] },
[:not, [:like, :u__name, 'Aeonax']]]
Sub Querry
Possible structures:
Constructor
instanceHash
with :select
value
Select
Possible structures:
constructor.select "name, email"
constructor.select :birthdate
constructor.select [:phone, 'role']
constructor.select [:_nullif, :row, :value]
constructor.select _missed_calls_count:
{ select: [:_count, [:_nullif, :connected, :true]],
from: [:calls, :c],
where: ['c.client_id = u.id',
['direction = ?', 0]]}
constructor.select m: [:common_rating, :work_rating, { _master_id: :id }]
constructor.structure
constructor.build_sql
Distinct Select
To specify distinct select you should add to your data structure :distinct
value:
true
- columns
Or with Constructor
instance methods: .distinct
- distinct structure - optional
.distinct_select
- select structure
- distinct structure - optional
In constructor methods
distinct: true
passed by default
distinct: true, select: true
constructor.distinct_select([:id, :name]).build_sql
constructor.distinct_select([:id, :name], :phone).build_sql
From
Possible structures:
from: 'table_name table_alias' || :table_name
from: [:table_name, :table_alias]
from: [:_values, [1, 2], :rgl, [:zgl, :gl]]
from: [[:table_name, :table_alias], [:_values, [1, 2], :values_name, [*values_column_names]]]
I do not like the from
syntax, but how it can be made more convenient...?
Join
Possible structures:
String
- just passed in to JOIN #{structure}
Array
with values(same order):
Array
with combination of possible structures.
join: 'users u ON u.id = m.user_id'
join: ['users u ON u.id = m.user_id',
'skills s ON u.id = s.user_id']
join: [[:user, :u], { on: 'rgl = 123' }]
join: [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
[:master, { on: ['z = 12', 'mrgl = 12'] }]]
Order
Possible structures:
String
, Symbol
just insert it in "ORDER BY #{structure} "
- SQL function
Hash
- key - previsious possible structures.
- value - options representation
Symbol
- :last || :first || :asc || :desc
String
- '<' || '>'
or smthng else that passed in to USING
Array
- with symbols inside
Array
- any possible structures
order: 'z.amount DESC' || :name
order: [:name, [:_case ...], { amount: [:first, :desc] }]
With
with: [:alias_name, { select: true, from: :users}]
with: [[:alias_name, { select: true, from: :users}],
[:alias_name_2, { select: {u: [:name, :email]},
from: :rgl}]]
with name: { select: true },
another_name: { select: :mrgl }
Union
union: [{ select: true, from: [:o_list, [:_values, [1], :al, [:master]]] },
{ select: true, from: [:co_list, [:_values, [0, :FALSE, :TRUE, 0],
:al, [:rating, :rejected,
:payment, :master]]] }]
'SELECT ... UNION SELECT ...'
SQL Functions
Params
Pass argument as params to adapter
[:_values, [:_params, 'mrgl', true, 'rgl'], :z, [:f_1, :f_2, :f_3]]
['(VALUES($1, $2, $3) AS z (f_1, f_2, f_3))', ['mrgl', true, 'rgl']]
Values
Second and third arguments are nesessary right now, but in furure i'll do them optional.
Arguments:
- array of values, can be nested
AS
name- column names
[:_values, [{u: [:name, :phone]}, :role, [:_params, 'rgl']], :z, [:n, :p, :r, :m]]
Result representation
::Row
Result row - allow accessing data by field name via method - row.name # => "mrgl"
or get hash representation with row.to_h
Contains
values
pg_result
- ::Result
instance
::Result < ::Row
Data can be accessed like from row - it use first row, or you can iterate rows.
Methods each
, each_with_index
, first
, last
, map
, count
, present?
delegates to rows
. index
delegates to fields
.
For data representation as Hash
or Array
exists method present
After initialize PG::Result
cleared
Contains
rows
- Array of ::Row
fields
- Array of column/alias names of returned dataoptions
Options
:types
- hash representing which column require additional parsing and which type:presenter_options
::Presenter
Used in result.present(options = {})
for data representation as Hash
or Array
. Options are merged with result.options[:presenter_options]
Data will be presented as Array
if rows > 1
or options[:array]
present.
Available options
array
- if querry returns only one row, but on client you await for array of data.structure
- Hash
with field as key and value as modifier. Modifier will rewrite field value in result. Modifier acts:
Proc
- it will call proc with row as argument, and! then pass it to modifier_presentation again::Result
- it will call modifier.present
- any else will be returned without changes
delete
- (will be soon) - Symbol, String or Array representing keys that must be deleted from result data.
TODO:
Except this todo's there is a lot commented todo's inside project.-_-
Tests
Meditate
Updates:
0.0.3
- Expand predicates syntax
- added support of multiple rows for
VALUES
ORDER
structure- scary tests-_-
Contact
You can write me your suggestions for improving the syntax, wishes, things that you think are missing here.
My email, Ruby On Rails slack