prequel 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README.md +0 -0
- data/lib/prequel.rb +38 -0
- data/lib/prequel/composite_tuple.rb +33 -0
- data/lib/prequel/core_extensions.rb +62 -0
- data/lib/prequel/expressions.rb +12 -0
- data/lib/prequel/expressions/aliased_expression.rb +15 -0
- data/lib/prequel/expressions/column.rb +35 -0
- data/lib/prequel/expressions/derived_column.rb +24 -0
- data/lib/prequel/expressions/equal.rb +22 -0
- data/lib/prequel/expressions/expression.rb +10 -0
- data/lib/prequel/expressions/set_function.rb +24 -0
- data/lib/prequel/field.rb +10 -0
- data/lib/prequel/record.rb +50 -0
- data/lib/prequel/relations.rb +11 -0
- data/lib/prequel/relations/inner_join.rb +39 -0
- data/lib/prequel/relations/projection.rb +86 -0
- data/lib/prequel/relations/relation.rb +71 -0
- data/lib/prequel/relations/selection.rb +31 -0
- data/lib/prequel/relations/table.rb +56 -0
- data/lib/prequel/session.rb +12 -0
- data/lib/prequel/sql.rb +13 -0
- data/lib/prequel/sql/derived_query_column.rb +23 -0
- data/lib/prequel/sql/inner_joined_table_ref.rb +23 -0
- data/lib/prequel/sql/named_table_ref.rb +19 -0
- data/lib/prequel/sql/query.rb +110 -0
- data/lib/prequel/sql/query_column.rb +22 -0
- data/lib/prequel/sql/subquery.rb +25 -0
- data/lib/prequel/sql/table_ref.rb +26 -0
- data/lib/prequel/tuple.rb +64 -0
- data/lib/prequel/version.rb +3 -0
- metadata +119 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by Nathan Sobo
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
File without changes
|
data/lib/prequel.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'sequel'
|
3
|
+
require 'prequel/version'
|
4
|
+
|
5
|
+
module Prequel
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def const_missing(name)
|
10
|
+
if name == :DB
|
11
|
+
const_set(:DB, Sequel::DATABASES.first)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def table(name, &block)
|
18
|
+
Relations::Table.new(name, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def session
|
22
|
+
Thread.current[:prequel_session] ||= Session.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear_session
|
26
|
+
Thread.current[:prequel_session] = nil if Thread.current[:prequel_session]
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'prequel/core_extensions'
|
30
|
+
autoload :CompositeTuple
|
31
|
+
autoload :Expressions
|
32
|
+
autoload :Field
|
33
|
+
autoload :Record
|
34
|
+
autoload :Relations
|
35
|
+
autoload :Session
|
36
|
+
autoload :Sql
|
37
|
+
autoload :Tuple
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Prequel
|
2
|
+
class CompositeTuple
|
3
|
+
attr_reader :left, :right
|
4
|
+
|
5
|
+
def initialize(left, right)
|
6
|
+
@left, @right = left, right
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
get_record(name) || get_field_value(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_record(table_name)
|
14
|
+
left.get_record(table_name) || right.get_record(table_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_field_value(name)
|
18
|
+
if name =~ /(.+)__(.+)/
|
19
|
+
table_name = $1.to_sym
|
20
|
+
field_name = $2.to_sym
|
21
|
+
get_record(table_name).try(:get_field_value, field_name)
|
22
|
+
else
|
23
|
+
left.get_field_value(name) || right.get_field_value(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def field_values
|
28
|
+
[left.field_values, right.field_values]
|
29
|
+
end
|
30
|
+
|
31
|
+
delegate :inspect, :to => :field_values
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Prequel
|
2
|
+
module HashExtensions
|
3
|
+
def to_predicate
|
4
|
+
raise NotImplementedError unless size == 1
|
5
|
+
keys.first.eq(values.first)
|
6
|
+
end
|
7
|
+
|
8
|
+
Hash.send(:include, self)
|
9
|
+
end
|
10
|
+
|
11
|
+
module SymbolExtensions
|
12
|
+
def as(alias_name)
|
13
|
+
"#{self}___#{alias_name}".to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def eq(other)
|
17
|
+
Expressions::Equal.new(self, other)
|
18
|
+
end
|
19
|
+
|
20
|
+
def count
|
21
|
+
Expressions::SetFunction.new(self, :count)
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_in_relations(relations)
|
25
|
+
if self =~ /^(.+)___(.+)$/
|
26
|
+
column_name = $1.to_sym
|
27
|
+
alias_name = $2.to_sym
|
28
|
+
Expressions::AliasedExpression.new(column_name, alias_name).resolve_in_relations(relations)
|
29
|
+
else
|
30
|
+
relations.each do |relation|
|
31
|
+
if column = relation.get_column(self)
|
32
|
+
return column
|
33
|
+
end
|
34
|
+
end
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_sql
|
40
|
+
inspect
|
41
|
+
end
|
42
|
+
|
43
|
+
Symbol.send(:include, self)
|
44
|
+
end
|
45
|
+
|
46
|
+
module PrimitiveExtensions
|
47
|
+
def resolve_in_relations(relations)
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_in_query(query)
|
52
|
+
query.add_literal(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
Numeric.send(:include, self)
|
56
|
+
String.send(:include, self)
|
57
|
+
TrueClass.send(:include, self)
|
58
|
+
FalseClass.send(:include, self)
|
59
|
+
NilClass.send(:include, self)
|
60
|
+
Time.send(:include, self)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Expressions
|
3
|
+
class AliasedExpression
|
4
|
+
attr_reader :expression, :alias_name
|
5
|
+
|
6
|
+
def initialize(expression, alias_name)
|
7
|
+
@expression, @alias_name = expression, alias_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve_in_relations(relations)
|
11
|
+
AliasedExpression.new(expression.resolve_in_relations(relations), alias_name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Expressions
|
3
|
+
class Column
|
4
|
+
attr_reader :table, :name, :type
|
5
|
+
|
6
|
+
def initialize(table, name, type)
|
7
|
+
@table, @name, @type = table, name, type
|
8
|
+
end
|
9
|
+
|
10
|
+
def alias_name
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def eq(other)
|
15
|
+
Equal.new(self, other)
|
16
|
+
end
|
17
|
+
|
18
|
+
def qualified_name
|
19
|
+
"#{table.name}__#{name}".to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def expression
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def origin
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolve_in_query(query)
|
31
|
+
query.singular_table_refs[table].resolve_column(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Expressions
|
3
|
+
class DerivedColumn
|
4
|
+
attr_reader :relation, :expression, :alias_name
|
5
|
+
delegate :origin, :to => :expression
|
6
|
+
|
7
|
+
def initialize(relation, expression, alias_name)
|
8
|
+
@relation, @expression, @alias_name = relation, expression, alias_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
alias_name || expression.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve_in_query(query)
|
16
|
+
if subquery = query.singular_table_refs[relation]
|
17
|
+
subquery.resolve_derived_column(self)
|
18
|
+
else
|
19
|
+
expression.resolve_in_query(query)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Expressions
|
3
|
+
class Equal
|
4
|
+
attr_reader :left, :right
|
5
|
+
def initialize(left, right)
|
6
|
+
@left, @right = left, right
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolve_in_relations(relations)
|
10
|
+
Equal.new(left.resolve_in_relations(relations), right.resolve_in_relations(relations))
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve_in_query(query)
|
14
|
+
Equal.new(left.resolve_in_query(query), right.resolve_in_query(query))
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_sql
|
18
|
+
"#{left.to_sql} = #{right.to_sql}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Expressions
|
3
|
+
class SetFunction < Expression
|
4
|
+
attr_reader :expression, :type
|
5
|
+
|
6
|
+
def initialize(expression, type)
|
7
|
+
@expression, @type = expression, type
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve_in_relations(relations)
|
11
|
+
SetFunction.new(expression.resolve_in_relations(relations), type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve_in_query(query)
|
15
|
+
SetFunction.new(expression.resolve_in_query(query), type)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_sql
|
19
|
+
"#{type}(#{expression.to_sql})"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Prequel
|
2
|
+
class Record < Tuple
|
3
|
+
class << self
|
4
|
+
delegate :all, :result_set, :[], :to_sql, :get_column, :first, :find, :where, :join, :project, :to => :relation
|
5
|
+
|
6
|
+
def table
|
7
|
+
relation
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_relation
|
11
|
+
relation
|
12
|
+
end
|
13
|
+
|
14
|
+
def inherited(klass)
|
15
|
+
table_name = klass.name.demodulize.underscore.pluralize.to_sym
|
16
|
+
klass.relation = Relations::Table.new(table_name, klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
def def_field_accessor(name)
|
20
|
+
def_field_reader(name)
|
21
|
+
def_field_writer(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def def_field_writer(name)
|
25
|
+
define_method("#{name}=") do |value|
|
26
|
+
set_field_value(name, value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def column(name, type)
|
31
|
+
relation.def_column(name, type)
|
32
|
+
def_field_accessor(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def new(field_values={})
|
36
|
+
Prequel.session[table.name][field_values[:id]] ||= super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def table
|
41
|
+
relation
|
42
|
+
end
|
43
|
+
|
44
|
+
public :set_field_value
|
45
|
+
|
46
|
+
def get_record(table_name)
|
47
|
+
self if table_name == table.name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Relations
|
3
|
+
class InnerJoin < Relation
|
4
|
+
attr_reader :left, :right, :predicate
|
5
|
+
|
6
|
+
def initialize(left_operand, right_operand, predicate)
|
7
|
+
@left, @right = left_operand.to_relation, right_operand.to_relation
|
8
|
+
@predicate = resolve(predicate.to_predicate)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_table(name)
|
12
|
+
left.get_table(name) || right.get_table(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def columns
|
16
|
+
(left.columns + right.columns).map do |column|
|
17
|
+
derive(column)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit(query)
|
22
|
+
query.table_ref = table_ref(query)
|
23
|
+
query.select_list = columns.map do |derived_column|
|
24
|
+
query.resolve_derived_column(derived_column, :qualified)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def table_ref(query)
|
29
|
+
Sql::InnerJoinedTableRef.new(left.table_ref(query), right.singular_table_ref(query), predicate.resolve_in_query(query))
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def operands
|
35
|
+
[left, right]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Relations
|
3
|
+
class Projection < Relation
|
4
|
+
attr_reader :operand
|
5
|
+
|
6
|
+
def initialize(operand, *symbols)
|
7
|
+
@operand = operand
|
8
|
+
assign_derived_columns(symbols)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_column(name)
|
12
|
+
if name.to_s.include?("__")
|
13
|
+
super
|
14
|
+
else
|
15
|
+
derived_columns_by_name[name]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def columns
|
20
|
+
derived_columns.values
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit(query)
|
24
|
+
operand.visit(query)
|
25
|
+
query.select_list = columns.map do |derived_column|
|
26
|
+
query.resolve_derived_column(derived_column)
|
27
|
+
end
|
28
|
+
|
29
|
+
if projected_table
|
30
|
+
query.tuple_builder = query.singular_table_refs[projected_table]
|
31
|
+
else
|
32
|
+
query.tuple_builder = self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_tuple(field_values)
|
37
|
+
tuple_class.new(field_values)
|
38
|
+
end
|
39
|
+
|
40
|
+
def tuple_class
|
41
|
+
@tuple_class ||= Class.new(Tuple).tap do |tuple_class|
|
42
|
+
tuple_class.relation = self
|
43
|
+
columns.each do |column|
|
44
|
+
tuple_class.def_field_reader(column.name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
attr_reader :projected_table
|
51
|
+
|
52
|
+
def assign_derived_columns(expressions)
|
53
|
+
if @projected_table = detect_projected_table(expressions)
|
54
|
+
projected_table.columns.map do |column|
|
55
|
+
derive(resolve(column.qualified_name.as(column.name)))
|
56
|
+
end
|
57
|
+
else
|
58
|
+
expressions.each do |column_name|
|
59
|
+
derive(resolve(column_name))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def detect_projected_table(args)
|
65
|
+
return false unless args.size == 1
|
66
|
+
arg = args.first
|
67
|
+
if arg.instance_of?(Table)
|
68
|
+
table_name = arg.name
|
69
|
+
elsif arg.instance_of?(Class) && arg.respond_to?(:table)
|
70
|
+
table_name = arg.table.name
|
71
|
+
elsif arg.instance_of?(Symbol)
|
72
|
+
return false if arg =~ /__/
|
73
|
+
table_name = arg
|
74
|
+
else
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
operand.get_table(table_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def operands
|
82
|
+
[operand]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Relations
|
3
|
+
class Relation
|
4
|
+
delegate :to_sql, :result_set, :all, :first, :to => :query
|
5
|
+
|
6
|
+
def query
|
7
|
+
Sql::Query.new(self).build
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(id)
|
11
|
+
where(:id => id).first
|
12
|
+
end
|
13
|
+
|
14
|
+
def where(predicate)
|
15
|
+
Selection.new(self, predicate)
|
16
|
+
end
|
17
|
+
|
18
|
+
def join(right, predicate)
|
19
|
+
InnerJoin.new(self, right, predicate)
|
20
|
+
end
|
21
|
+
|
22
|
+
def project(*symbols)
|
23
|
+
Projection.new(self, *symbols)
|
24
|
+
end
|
25
|
+
|
26
|
+
def table_ref(query)
|
27
|
+
singular_table_ref(query)
|
28
|
+
end
|
29
|
+
|
30
|
+
def singular_table_ref(query)
|
31
|
+
query.add_subquery(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_relation
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_column(name)
|
39
|
+
resolved = resolve(name)
|
40
|
+
derive(resolved) if resolved
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def resolve(expression)
|
46
|
+
expression.resolve_in_relations(operands)
|
47
|
+
end
|
48
|
+
|
49
|
+
def derive(resolved_expression)
|
50
|
+
if resolved_expression.instance_of?(Expressions::AliasedExpression)
|
51
|
+
alias_name = resolved_expression.alias_name
|
52
|
+
resolved_expression = resolved_expression.expression
|
53
|
+
end
|
54
|
+
|
55
|
+
derived_columns[resolved_expression] ||=
|
56
|
+
Expressions::DerivedColumn.new(self, resolved_expression, alias_name).tap do |derived_column|
|
57
|
+
derived_columns[resolved_expression] = derived_column
|
58
|
+
derived_columns_by_name[derived_column.name] = derived_column
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def derived_columns
|
63
|
+
@derived_columns ||= {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def derived_columns_by_name
|
67
|
+
@derived_columns_by_name ||= {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Relations
|
3
|
+
class Selection < Relation
|
4
|
+
attr_reader :operand, :predicate
|
5
|
+
|
6
|
+
def initialize(operand, predicate)
|
7
|
+
@operand = operand
|
8
|
+
@predicate = resolve(predicate.to_predicate)
|
9
|
+
end
|
10
|
+
|
11
|
+
delegate :get_table, :to => :operand
|
12
|
+
|
13
|
+
def columns
|
14
|
+
operand.columns.map do |column|
|
15
|
+
derive(column)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit(query)
|
20
|
+
operand.visit(query)
|
21
|
+
query.add_condition(predicate.resolve_in_query(query))
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def operands
|
27
|
+
[operand]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Relations
|
3
|
+
class Table < Relation
|
4
|
+
attr_reader :name, :columns_by_name, :tuple_class
|
5
|
+
|
6
|
+
def initialize(name, tuple_class=nil, &block)
|
7
|
+
@name, @tuple_class = name, tuple_class
|
8
|
+
@columns_by_name = {}
|
9
|
+
TableDefinitionContext.new(self).instance_eval(&block) if block
|
10
|
+
end
|
11
|
+
|
12
|
+
def def_column(name, type)
|
13
|
+
columns_by_name[name] = Expressions::Column.new(self, name, type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](col_name)
|
17
|
+
"#{name}__#{col_name}".to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_column(column_name)
|
21
|
+
if column_name.match(/(.+)__(.+)/)
|
22
|
+
qualifier, column_name = $1.to_sym, $2.to_sym
|
23
|
+
return nil unless qualifier == name
|
24
|
+
end
|
25
|
+
columns_by_name[column_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_table(table_name)
|
29
|
+
self if name == table_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def columns
|
33
|
+
columns_by_name.values
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit(query)
|
37
|
+
query.table_ref = table_ref(query)
|
38
|
+
end
|
39
|
+
|
40
|
+
def singular_table_ref(query)
|
41
|
+
query.add_singular_table_ref(self, Sql::TableRef.new(self))
|
42
|
+
end
|
43
|
+
|
44
|
+
class TableDefinitionContext
|
45
|
+
attr_reader :table
|
46
|
+
def initialize(table)
|
47
|
+
@table = table
|
48
|
+
end
|
49
|
+
|
50
|
+
def column(name, type)
|
51
|
+
table.def_column(name, type)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/prequel/sql.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class DerivedQueryColumn
|
4
|
+
attr_reader :subquery, :name, :expression
|
5
|
+
|
6
|
+
def initialize(subquery, name, expression)
|
7
|
+
@subquery, @name, @expression = subquery, name, expression
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_sql
|
11
|
+
"#{subquery.name}.#{name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_select_clause_sql
|
15
|
+
"#{expression.to_sql} as #{name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def qualified_name
|
19
|
+
"#{subquery.name}__#{name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class InnerJoinedTableRef
|
4
|
+
attr_reader :left, :right, :predicate
|
5
|
+
def initialize(left, right, predicate)
|
6
|
+
@left, @right, @predicate = left, right, predicate
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_sql
|
10
|
+
[left.to_sql,
|
11
|
+
'inner join',
|
12
|
+
right.to_sql,
|
13
|
+
'on',
|
14
|
+
predicate.to_sql
|
15
|
+
].join(' ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_tuple(field_values)
|
19
|
+
CompositeTuple.new(left.build_tuple(field_values), right.build_tuple(field_values))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
module NamedTableRef
|
4
|
+
protected
|
5
|
+
|
6
|
+
def extract_field_values(field_values)
|
7
|
+
{}.tap do |specific_field_values|
|
8
|
+
field_values.each do |field_name, value|
|
9
|
+
if field_name =~ /(.+?)__(.+)/
|
10
|
+
qualifier, field_name = $1.to_sym, $2.to_sym
|
11
|
+
next unless qualifier == name
|
12
|
+
end
|
13
|
+
specific_field_values[field_name] = value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class Query
|
4
|
+
attr_accessor :select_list
|
5
|
+
attr_reader :relation, :table_ref, :conditions, :literals, :singular_table_refs, :subquery_count, :query_columns
|
6
|
+
attr_writer :tuple_builder
|
7
|
+
|
8
|
+
def initialize(relation)
|
9
|
+
@relation = relation
|
10
|
+
@conditions = []
|
11
|
+
@literals = {}
|
12
|
+
@singular_table_refs = { relation => self }
|
13
|
+
@subquery_count = 0
|
14
|
+
@query_columns = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
result_set.map do |field_values|
|
19
|
+
tuple_builder.build_tuple(field_values)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def first
|
24
|
+
r = result_set
|
25
|
+
r.empty?? nil : tuple_builder.build_tuple(r.first)
|
26
|
+
end
|
27
|
+
|
28
|
+
def result_set
|
29
|
+
DB[*to_sql]
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sql
|
33
|
+
[sql_string, literals]
|
34
|
+
end
|
35
|
+
|
36
|
+
def build
|
37
|
+
relation.visit(self)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def table_ref=(table_ref)
|
42
|
+
raise "A table ref has already been assigned" if @table_ref
|
43
|
+
@table_ref = table_ref
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_condition(predicate)
|
47
|
+
conditions.push(predicate)
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_literal(literal)
|
51
|
+
"v#{literals.size + 1}".to_sym.tap do |placeholder|
|
52
|
+
literals[placeholder] = literal
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_singular_table_ref(relation, table_ref)
|
57
|
+
singular_table_refs[relation] = table_ref
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_subquery(relation)
|
61
|
+
@subquery_count += 1
|
62
|
+
subquery = Subquery.new(self, relation, "t#{subquery_count}".to_sym)
|
63
|
+
add_singular_table_ref(relation, subquery)
|
64
|
+
subquery.build
|
65
|
+
end
|
66
|
+
|
67
|
+
def resolve_derived_column(column, qualified=false)
|
68
|
+
query_columns[column] ||= begin
|
69
|
+
resolved_expression = column.expression.resolve_in_query(self)
|
70
|
+
resolved_name = qualified ? resolved_expression.qualified_name : column.name
|
71
|
+
Sql::DerivedQueryColumn.new(self, resolved_name, resolved_expression)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def tuple_builder
|
76
|
+
@tuple_builder || table_ref
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def sql_string
|
82
|
+
["select",
|
83
|
+
select_clause_sql,
|
84
|
+
"from",
|
85
|
+
from_clause_sql,
|
86
|
+
where_clause_sql,
|
87
|
+
].compact.join(" ")
|
88
|
+
end
|
89
|
+
|
90
|
+
def select_clause_sql
|
91
|
+
if select_list
|
92
|
+
select_list.map {|column| column.to_select_clause_sql}.join(', ')
|
93
|
+
else
|
94
|
+
'*'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def from_clause_sql
|
99
|
+
table_ref.to_sql
|
100
|
+
end
|
101
|
+
|
102
|
+
def where_clause_sql
|
103
|
+
return nil if conditions.empty?
|
104
|
+
'where ' + conditions.map do |condition|
|
105
|
+
condition.to_sql
|
106
|
+
end.join(' and ')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class QueryColumn
|
4
|
+
attr_reader :table_ref, :name
|
5
|
+
def initialize(table_ref, name)
|
6
|
+
@table_ref, @name = table_ref, name
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_sql
|
10
|
+
"#{table_ref.name}.#{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_select_clause_sql
|
14
|
+
to_sql
|
15
|
+
end
|
16
|
+
|
17
|
+
def qualified_name
|
18
|
+
"#{table_ref.name}__#{name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class Subquery < Query
|
4
|
+
include NamedTableRef
|
5
|
+
|
6
|
+
attr_reader :parent, :relation, :name
|
7
|
+
delegate :columns, :to => :relation
|
8
|
+
|
9
|
+
def initialize(parent, relation, name)
|
10
|
+
@parent, @name = parent, name
|
11
|
+
super(relation)
|
12
|
+
end
|
13
|
+
|
14
|
+
delegate :add_literal, :add_singular_table_ref, :add_subquery, :singular_table_refs, :to => :parent
|
15
|
+
|
16
|
+
def to_sql
|
17
|
+
['(', sql_string, ') as ', name].join
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_tuple(field_values)
|
21
|
+
tuple_builder.build_tuple(extract_field_values(field_values))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Prequel
|
2
|
+
module Sql
|
3
|
+
class TableRef
|
4
|
+
include NamedTableRef
|
5
|
+
attr_reader :relation, :query_columns
|
6
|
+
delegate :name, :columns, :tuple_class, :to => :relation
|
7
|
+
|
8
|
+
def initialize(relation)
|
9
|
+
@relation = relation
|
10
|
+
@query_columns = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_sql
|
14
|
+
name
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve_column(column)
|
18
|
+
query_columns[column] ||= Sql::QueryColumn.new(self, column.name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_tuple(field_values)
|
22
|
+
tuple_class.new(extract_field_values(field_values))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Prequel
|
2
|
+
class Tuple
|
3
|
+
class_attribute :relation
|
4
|
+
|
5
|
+
class << self
|
6
|
+
delegate :columns, :to => :relation
|
7
|
+
|
8
|
+
def def_field_reader(name)
|
9
|
+
define_method(name) do
|
10
|
+
get_field_value(name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(values = {})
|
16
|
+
initialize_fields
|
17
|
+
soft_update_fields(values)
|
18
|
+
end
|
19
|
+
|
20
|
+
delegate :columns, :to => :relation
|
21
|
+
|
22
|
+
def soft_update_fields(values)
|
23
|
+
values.each do |name, value|
|
24
|
+
set_field_value(name, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_field_value(name)
|
29
|
+
fields_by_name[name].try(:value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def field_values
|
33
|
+
fields_by_name.inject({}) do |h, (name, field)|
|
34
|
+
h[name] = field.value
|
35
|
+
h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_record(name)
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
delegate :inspect, :to => :field_values
|
44
|
+
|
45
|
+
protected
|
46
|
+
attr_reader :fields_by_name
|
47
|
+
|
48
|
+
def initialize_fields
|
49
|
+
@fields_by_name = {}
|
50
|
+
columns.each do |column|
|
51
|
+
fields_by_name[column.name] = Field.new(self, column)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_field_value(name, value)
|
56
|
+
field = fields_by_name[name]
|
57
|
+
unless field
|
58
|
+
raise "No field found #{name.inspect}"
|
59
|
+
end
|
60
|
+
field.value = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: prequel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathan Sobo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-03-03 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: activesupport
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.0.4
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 3.20.0
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id003
|
49
|
+
description: Prequel is the database library I've always wanted.
|
50
|
+
email:
|
51
|
+
- nathansobo@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- lib/prequel/composite_tuple.rb
|
60
|
+
- lib/prequel/core_extensions.rb
|
61
|
+
- lib/prequel/expressions/aliased_expression.rb
|
62
|
+
- lib/prequel/expressions/column.rb
|
63
|
+
- lib/prequel/expressions/derived_column.rb
|
64
|
+
- lib/prequel/expressions/equal.rb
|
65
|
+
- lib/prequel/expressions/expression.rb
|
66
|
+
- lib/prequel/expressions/set_function.rb
|
67
|
+
- lib/prequel/expressions.rb
|
68
|
+
- lib/prequel/field.rb
|
69
|
+
- lib/prequel/record.rb
|
70
|
+
- lib/prequel/relations/inner_join.rb
|
71
|
+
- lib/prequel/relations/projection.rb
|
72
|
+
- lib/prequel/relations/relation.rb
|
73
|
+
- lib/prequel/relations/selection.rb
|
74
|
+
- lib/prequel/relations/table.rb
|
75
|
+
- lib/prequel/relations.rb
|
76
|
+
- lib/prequel/session.rb
|
77
|
+
- lib/prequel/sql/derived_query_column.rb
|
78
|
+
- lib/prequel/sql/inner_joined_table_ref.rb
|
79
|
+
- lib/prequel/sql/named_table_ref.rb
|
80
|
+
- lib/prequel/sql/query.rb
|
81
|
+
- lib/prequel/sql/query_column.rb
|
82
|
+
- lib/prequel/sql/subquery.rb
|
83
|
+
- lib/prequel/sql/table_ref.rb
|
84
|
+
- lib/prequel/sql.rb
|
85
|
+
- lib/prequel/tuple.rb
|
86
|
+
- lib/prequel/version.rb
|
87
|
+
- lib/prequel.rb
|
88
|
+
- LICENSE
|
89
|
+
- README.md
|
90
|
+
has_rdoc: true
|
91
|
+
homepage: http://github.com/nathansobo/prequel
|
92
|
+
licenses: []
|
93
|
+
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: "0"
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: "0"
|
111
|
+
requirements: []
|
112
|
+
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.5.2
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: A ground-up relational algebraic ORM.
|
118
|
+
test_files: []
|
119
|
+
|