seaquel 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/FEATURES +5 -0
- data/LICENSE +23 -0
- data/README +25 -0
- data/lib/seaquel.rb +13 -0
- data/lib/seaquel/ast.rb +19 -0
- data/lib/seaquel/ast/alias.rb +15 -0
- data/lib/seaquel/ast/assign.rb +17 -0
- data/lib/seaquel/ast/bin_op.rb +21 -0
- data/lib/seaquel/ast/binding.rb +20 -0
- data/lib/seaquel/ast/column.rb +50 -0
- data/lib/seaquel/ast/column_list.rb +11 -0
- data/lib/seaquel/ast/expression.rb +35 -0
- data/lib/seaquel/ast/funcall.rb +17 -0
- data/lib/seaquel/ast/immediate.rb +16 -0
- data/lib/seaquel/ast/join_op.rb +29 -0
- data/lib/seaquel/ast/list.rb +25 -0
- data/lib/seaquel/ast/literal.rb +16 -0
- data/lib/seaquel/ast/node.rb +109 -0
- data/lib/seaquel/ast/order.rb +16 -0
- data/lib/seaquel/ast/table.rb +36 -0
- data/lib/seaquel/ast/table_alias.rb +24 -0
- data/lib/seaquel/bit.rb +34 -0
- data/lib/seaquel/expression_converter.rb +181 -0
- data/lib/seaquel/generator.rb +29 -0
- data/lib/seaquel/module.rb +39 -0
- data/lib/seaquel/quoter.rb +23 -0
- data/lib/seaquel/statement.rb +245 -0
- data/lib/seaquel/statement/join.rb +36 -0
- data/lib/seaquel/statement_gatherer.rb +119 -0
- data/qed/applique/ae.rb +1 -0
- data/qed/applique/sql.rb +2 -0
- data/qed/generation.md +103 -0
- data/spec/functional/delete_spec.rb +19 -0
- data/spec/functional/insert_spec.rb +31 -0
- data/spec/functional/select_spec.rb +99 -0
- data/spec/functional/update_spec.rb +30 -0
- data/spec/lib/ast/column_spec.rb +53 -0
- data/spec/lib/ast/immediate_spec.rb +42 -0
- data/spec/spec_helper.rb +14 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f51aa6cb26dbabeed5e62e1544877377b44090bc
|
4
|
+
data.tar.gz: 982a28e72454c041dd21129416c7def66b95ff32
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d50b0346f4288a0974d2b0364b3f6596cde261fbcb126662a3aaf9f92fcd5c0255d688c5707afcf2b0d6d4639861427172aac652c20ed5347667175904c97290
|
7
|
+
data.tar.gz: cb41ddc3e7e14f77ab0f0774660fc66a414c5c6c4a4f02e7d42ad205a5ac1a5c6e32d08f48a88ea2c2a8189d6eaa1e9e12d338365a11ed6870791962ac1cef2f
|
data/FEATURES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2015 Kaspar Schiess
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person
|
5
|
+
obtaining a copy of this software and associated documentation
|
6
|
+
files (the "Software"), to deal in the Software without
|
7
|
+
restriction, including without limitation the rights to use,
|
8
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following
|
11
|
+
conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
A simple SQL generation DSL.
|
2
|
+
|
3
|
+
SYNOPSIS
|
4
|
+
|
5
|
+
include Seaquel
|
6
|
+
select.from(table('foobar')).where(column('name').equal('baz')).to_sql
|
7
|
+
# => %Q(SELECT * FROM "foobar" WHERE "name"='baz')
|
8
|
+
|
9
|
+
DESCRIPTION
|
10
|
+
|
11
|
+
Allows generating SQL from a Ruby DSL. Geared towards the rich possibilities of
|
12
|
+
PostgreSQL, this library can generate standard SQL as well.
|
13
|
+
|
14
|
+
Please also have a look at flounder, Seaquels friendly companion - located one
|
15
|
+
layer higher up our SQL stack, it automates much of the work you'll be doing
|
16
|
+
when you use Seaquel.
|
17
|
+
|
18
|
+
STATUS
|
19
|
+
|
20
|
+
Early alpha
|
21
|
+
|
22
|
+
WHY
|
23
|
+
|
24
|
+
Because nothing really worked at the time we wrote this. Really. We looked.
|
25
|
+
|
data/lib/seaquel.rb
ADDED
data/lib/seaquel/ast.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Seaquel::AST
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'seaquel/ast/node'
|
6
|
+
require 'seaquel/ast/bin_op'
|
7
|
+
require 'seaquel/ast/join_op'
|
8
|
+
require 'seaquel/ast/assign'
|
9
|
+
require 'seaquel/ast/alias'
|
10
|
+
require 'seaquel/ast/list'
|
11
|
+
require 'seaquel/ast/column_list'
|
12
|
+
require 'seaquel/ast/column'
|
13
|
+
require 'seaquel/ast/table'
|
14
|
+
require 'seaquel/ast/literal'
|
15
|
+
require 'seaquel/ast/immediate'
|
16
|
+
require 'seaquel/ast/binding'
|
17
|
+
require 'seaquel/ast/funcall'
|
18
|
+
require 'seaquel/ast/table_alias'
|
19
|
+
require 'seaquel/ast/order'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Seaquel::AST
|
2
|
+
# Assignment of right to left.
|
3
|
+
# This class can be visited.
|
4
|
+
#
|
5
|
+
class Assign
|
6
|
+
attr_reader :left, :right
|
7
|
+
|
8
|
+
def initialize left, right
|
9
|
+
@left, @right = left, right
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit visitor
|
13
|
+
visitor.visit_assign(left, right)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Seaquel::AST
|
3
|
+
# A binary statement as in left=right.
|
4
|
+
# This class can be visited.
|
5
|
+
#
|
6
|
+
class BinOp
|
7
|
+
attr_reader :op, :left, :right
|
8
|
+
|
9
|
+
def initialize op, left, right
|
10
|
+
@op, @left, @right = op, left, right
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit visitor
|
14
|
+
visitor.visit_binop(op, left, right)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"(" + ['bin_op', op.inspect, left.inspect, right.inspect].join(' ') + ")"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require_relative 'expression'
|
3
|
+
|
4
|
+
module Seaquel::AST
|
5
|
+
class Binding < Expression
|
6
|
+
attr_reader :pos
|
7
|
+
|
8
|
+
def initialize pos
|
9
|
+
@pos = pos
|
10
|
+
end
|
11
|
+
|
12
|
+
def visit visitor
|
13
|
+
visitor.visit_binding pos.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"(bind #{pos})"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
require_relative 'expression'
|
3
|
+
|
4
|
+
module Seaquel::AST
|
5
|
+
class Column < Expression
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :table
|
8
|
+
|
9
|
+
def initialize name, table=nil
|
10
|
+
@name = name
|
11
|
+
@table = table
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set the column to value.
|
15
|
+
#
|
16
|
+
def to value
|
17
|
+
Assign.new(self, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Visits the column as part of sql generation.
|
21
|
+
#
|
22
|
+
def visit visitor
|
23
|
+
visitor.visit_column self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an SQL column reference, including the table name.
|
27
|
+
#
|
28
|
+
def as_full_reference quoter
|
29
|
+
parts = []
|
30
|
+
|
31
|
+
if table
|
32
|
+
parts << table.as_column_prefix(quoter)
|
33
|
+
end
|
34
|
+
|
35
|
+
parts << as_column_reference(quoter)
|
36
|
+
|
37
|
+
parts.join('.')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an SQL column reference, excluding table name.
|
41
|
+
#
|
42
|
+
def as_column_reference quoter
|
43
|
+
quoter.column(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect
|
47
|
+
"(column #{name})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
module Seaquel::AST
|
3
|
+
# Base class for all expression-type AST nodes.
|
4
|
+
#
|
5
|
+
class Expression
|
6
|
+
def self.binop op
|
7
|
+
define_method(op) do |exp|
|
8
|
+
BinOp.new(op, self, exp)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
binop :eq
|
13
|
+
binop :noteq
|
14
|
+
|
15
|
+
binop :lt
|
16
|
+
binop :lteq
|
17
|
+
|
18
|
+
binop :gt
|
19
|
+
binop :gteq
|
20
|
+
|
21
|
+
binop :is
|
22
|
+
binop :isnot
|
23
|
+
|
24
|
+
def as name
|
25
|
+
Alias.new(name, self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def asc
|
29
|
+
Order.new(:asc, self)
|
30
|
+
end
|
31
|
+
def desc
|
32
|
+
Order.new(:desc, self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require_relative 'expression'
|
3
|
+
|
4
|
+
module Seaquel::AST
|
5
|
+
class Funcall < Expression
|
6
|
+
attr_reader :name, :args
|
7
|
+
|
8
|
+
def initialize name, args
|
9
|
+
@name = name
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit visitor
|
14
|
+
visitor.visit_funcall(name, args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Seaquel::AST
|
2
|
+
# A binary operation that can be used to join things together, like 'AND' or
|
3
|
+
# 'OR'.
|
4
|
+
#
|
5
|
+
class JoinOp
|
6
|
+
attr_reader :op
|
7
|
+
attr_reader :elements
|
8
|
+
|
9
|
+
def initialize op, elements=[]
|
10
|
+
@op, @elements = op, elements
|
11
|
+
end
|
12
|
+
|
13
|
+
def concat element
|
14
|
+
elements << element
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
elements.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit visitor
|
22
|
+
visitor.visit_joinop(op, elements)
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
[:join_op, op, *elements].inspect
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Seaquel::AST
|
5
|
+
class List
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :list
|
9
|
+
|
10
|
+
def initialize initial=[]
|
11
|
+
@list = initial
|
12
|
+
end
|
13
|
+
|
14
|
+
# Behaves array-like in many respects
|
15
|
+
def_delegators :@list, :empty?, :concat, :size, :<<, :map, :replace
|
16
|
+
|
17
|
+
def visit visitor
|
18
|
+
visitor.visit_list(list)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
'[' + list.map { |e| e.inspect }.join(', ') + ']'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Seaquel::AST
|
2
|
+
# A general purpose node containing a node type and a list of arguments.
|
3
|
+
# The node stores a link to its parent or nil if no such parent exists.
|
4
|
+
# This class can be visited.
|
5
|
+
#
|
6
|
+
class Node
|
7
|
+
attr_reader :type
|
8
|
+
attr_reader :parent
|
9
|
+
attr_reader :args
|
10
|
+
|
11
|
+
def initialize *args
|
12
|
+
if args.first.kind_of?(Symbol)
|
13
|
+
@type, *@args = args
|
14
|
+
else
|
15
|
+
@parent, @type, *@args = args
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Replaces previous projection list with this one.
|
20
|
+
#
|
21
|
+
# @param list [Array<Column>]
|
22
|
+
# @return [Node] node for chaining
|
23
|
+
#
|
24
|
+
def project *fields
|
25
|
+
node(:project, fields)
|
26
|
+
end
|
27
|
+
|
28
|
+
def from *tables
|
29
|
+
node(:from, *tables)
|
30
|
+
end
|
31
|
+
|
32
|
+
def where *exps
|
33
|
+
node(:where, JoinOp.new(:and, exps))
|
34
|
+
end
|
35
|
+
|
36
|
+
def set *assign_list
|
37
|
+
node(:set, assign_list)
|
38
|
+
end
|
39
|
+
|
40
|
+
def join *tables
|
41
|
+
node(:join, tables)
|
42
|
+
end
|
43
|
+
def on *exps
|
44
|
+
node(:on, exps)
|
45
|
+
end
|
46
|
+
|
47
|
+
def limit n
|
48
|
+
node(:limit, n)
|
49
|
+
end
|
50
|
+
def offset n
|
51
|
+
node(:offset, n)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Replaces previous ORDER BY specification with this one.
|
55
|
+
#
|
56
|
+
# @param list [Array<Column>]
|
57
|
+
# @return [Node] node for chaining
|
58
|
+
#
|
59
|
+
def order_by *list
|
60
|
+
node(:order_by, list)
|
61
|
+
end
|
62
|
+
|
63
|
+
# INSERT INTO ... (FIELDS) VALUES (...)
|
64
|
+
def fields *list
|
65
|
+
node(:fields, list)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Adds a values list to an INSERT statement. You have to pass all the columns
|
69
|
+
# at once; repeating this method call will create an INSERT statement that
|
70
|
+
# inserts multiple rows at once (a Postgres extension).
|
71
|
+
#
|
72
|
+
# Example:
|
73
|
+
# include Seaquel
|
74
|
+
# insert.into(table('foo')).values(immediate(1), immediate(2))
|
75
|
+
# # => INSERT INTO "foo" VALUES (1, 2)
|
76
|
+
#
|
77
|
+
# @param list [Array<AST::Expression>] values to insert
|
78
|
+
# @return [AST::Node] query you are building
|
79
|
+
#
|
80
|
+
def values *list
|
81
|
+
node(:values, list)
|
82
|
+
end
|
83
|
+
|
84
|
+
def into table
|
85
|
+
node(:into, table)
|
86
|
+
end
|
87
|
+
|
88
|
+
def node *args
|
89
|
+
self.class.new(self, *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
def visit visitor
|
93
|
+
method_name = "visit_#{type}"
|
94
|
+
if visitor.respond_to?(:visit_node)
|
95
|
+
visitor.visit_node(self)
|
96
|
+
else
|
97
|
+
visitor.send(method_name, parent, *args)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_sql
|
102
|
+
::Seaquel::Generator.new(self).compact_sql
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
[type, args, parent].inspect
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|