mystic 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pg"
4
+ require "access_stack"
5
+
6
+ module Mystic
7
+ class Postgres
8
+ CONNECT_FIELDS = [
9
+ :host,
10
+ :hostaddr,
11
+ :port,
12
+ :dbname,
13
+ :user,
14
+ :password,
15
+ :connect_timeout,
16
+ :options,
17
+ :tty,
18
+ :sslmode,
19
+ :krbsrvname,
20
+ :gsslib
21
+ ].freeze
22
+
23
+ INDEX_TYPES = [
24
+ :btree,
25
+ :hash,
26
+ :gist,
27
+ :spgist,
28
+ :gin
29
+ ].freeze
30
+
31
+ def initialize opts={}
32
+ return if opts.empty?
33
+ @pool = AccessStack.new(
34
+ :size => opts[:pool] || 5,
35
+ :timeout => opts[:timeout] || 30,
36
+ :expires => opts[:expires],
37
+ :create => lambda { create_pg opts },
38
+ :destroy => lambda { |pg| pg.close },
39
+ :validate => lambda { |pg| pg != nil && pg.status == CONNECTION_OK }
40
+ )
41
+ end
42
+
43
+ alias_method :connect, :initialize
44
+
45
+ def pool_size= v
46
+ @pool.size = v
47
+ end
48
+
49
+ def disconnect
50
+ @pool.empty!
51
+ end
52
+
53
+ def reap!
54
+ @pool.reap!
55
+ end
56
+
57
+ def connected?
58
+ !@pool.empty?
59
+ end
60
+
61
+ def escape str
62
+ @pool.with { |pg| pg.escape_string str }
63
+ end
64
+
65
+ def execute sql
66
+ res = @pool.with { |pg| pg.exec sql }
67
+ v = res[0][Mystic::JSON_COL] if res.ntuples == 1 && res.nfields == 1
68
+ v ||= res.ntuples.times.map { |i| res[i] } unless res.nil?
69
+ v ||= []
70
+ v
71
+ end
72
+
73
+ def create_pg opts
74
+ pg = PG.connect opts.subhash(*CONNECT_FIELDS)
75
+ pg.set_notice_receiver { |r| }
76
+ pg
77
+ end
78
+ end
79
+ end
@@ -2,243 +2,9 @@
2
2
 
3
3
  module Mystic
4
4
  module SQL
5
- Error = Class.new(StandardError)
6
-
7
- class SQLObject
8
- def to_sql
9
- Mystic.adapter.serialize_sql self
10
- end
11
-
12
- alias_method :to_s, :to_sql
13
- end
14
-
15
- class Index < SQLObject
16
- attr_accessor :name, # Symbol or string
17
- :table_name, # Symbol or string
18
- :type, # Symbol
19
- :unique, # TrueClass/FalseClass
20
- :columns, # Array of Strings
21
- :opts # Hash, see below
22
-
23
- # opts
24
- # It's a Hash that represents options
25
- #
26
- # MYSQL ONLY
27
- # Key => Value (type)
28
- # :comment => A string that's up to 1024 chars (String)
29
- # :algorithm => The algorithm to use (Symbol)
30
- # :lock => The lock to use (Symbol)
31
- #
32
- # POSTGRES ONLY
33
- # Key => Value (type)
34
- # :fillfactor => A value in the range 10..100 (Integer)
35
- # :fastupdate => true/false (TrueClass/FalseClass)
36
- # :concurrently => true/false (TrueClass/FalseClass)
37
- # :tablespace => The name of the desired tablespace (String)
38
- # :buffering => :on/:off/:auto (Symbol)
39
- # :concurrently => true/false (TrueClass/FalseClass)
40
- # :where => The conditions for including entries in your index, same as SELECT * FROM table WHERE ____ (String)
41
-
42
- def initialize(opts={})
43
- opts.symbolize!
44
- raise ArgumentError, "Indeces need a table_name or else what's the point?." unless opts.member? :table_name
45
- raise ArgumentError, "Indeces need columns or else what's the point?" unless opts.member? :columns
46
- @name = opts.delete(:name).to_sym if opts.member? :name
47
- @table_name = opts.delete(:table_name).to_sym
48
- @type = (opts.delete :type || :btree).to_s.downcase.to_sym
49
- @unique = opts.delete :unique || false
50
- @columns = opts.delete(:columns).symbolize rescue []
51
- @opts = opts
52
- end
53
-
54
- # can accept shit other than columns like
55
- # box(location,location)
56
- def <<(col)
57
- case col
58
- when Column
59
- @columns << col.name.to_s
60
- when String
61
- @columns << col
62
- else
63
- raise ArgumentError, "Column must be a String or a Mystic::SQL::Column"
64
- end
65
- end
66
-
67
- alias_method :push, :<<
68
-
69
- def method_missing(meth, *args, &block)
70
- return @opts[meth] if @opts.member? meth
71
- nil
72
- end
73
- end
74
-
75
- class Column < SQLObject
76
- attr_accessor :name, :kind, :size, :constraints, :geom_kind, :geom_srid
77
-
78
- def initialize(opts={})
79
- @name = opts.delete(:name).to_s
80
- @kind = opts.delete(:kind).to_sym
81
- @size = opts.delete(:size).to_s if opts.member? :size
82
- @geom_kind = opts.delete(:geom_kind)
83
- @geom_srid = opts.delete(:geom_srid).to_i
84
- @constraints = opts
85
- end
86
-
87
- def geospatial?
88
- @geom_kind && @geom_srid
89
- end
90
- end
91
-
92
- class Table < SQLObject
93
- attr_reader :name
94
- attr_accessor :columns,
95
- :indeces,
96
- :operations,
97
- :opts
98
-
99
- def self.create(opts={})
100
- new true, opts
101
- end
102
-
103
- def self.alter(opts={})
104
- new false, opts
105
- end
106
-
107
- def initialize(is_create=true, opts={})
108
- @is_create = is_create
109
- @opts = opts.symbolize
110
- @columns = []
111
- @indeces = []
112
- @operations = []
113
-
114
- @name = @opts.delete(:name).to_s
115
- raise ArgumentError, "Argument 'name' is invalid." if @name.empty?
116
- end
117
-
118
- def create?
119
- @is_create
120
- end
121
-
122
- def <<(obj)
123
- case obj
124
- when Column
125
- @columns << obj
126
- when Index
127
- @indeces << obj
128
- when Operation
129
- @operations << obj
130
- else
131
- raise ArgumentError, "Argument is not a Mystic::SQL::Column, Mystic::SQL::Operation, or Mystic::SQL::Index."
132
- end
133
- end
134
-
135
- def to_sql
136
- raise ArgumentError, "Table cannot have zero columns." if @columns.empty?
137
- super
138
- end
139
-
140
- alias_method :push, :<<
141
-
142
- #
143
- ## Operation DSL
144
- #
145
-
146
- def drop_index(idx_name)
147
- raise Mystic::SQL::Error, "Cannot drop an index on a table that doesn't exist." if create?
148
- self << Mystic::SQL::Operation.drop_index(
149
- :index_name => idx_name.to_s,
150
- :table_name => self.name.to_s
151
- )
152
- end
153
-
154
- def rename_column(oldname, newname)
155
- raise Mystic::SQL::Error, "Cannot rename a column on a table that doesn't exist." if create?
156
- self << Mystic::SQL::Operation.rename_column(
157
- :table_name => self.name.to_s,
158
- :old_name => oldname.to_s,
159
- :new_name => newname.to_s
160
- )
161
- end
162
-
163
- def rename(newname)
164
- raise Mystic::SQL::Error, "Cannot rename a table that doesn't exist." if create?
165
- self << Mystic::SQL::Operation.rename_table(
166
- :old_name => self.name.dup.to_s,
167
- :new_name => newname.to_s,
168
- :callback => lambda { self.name = newname }
169
- )
170
- end
171
-
172
- def drop_columns(*col_names)
173
- raise Mystic::SQL::Error, "Cannot drop a column(s) on a table that doesn't exist." if create?
174
- self << Mystic::SQL::Operation.drop_columns(
175
- :table_name => self.name.to_s,
176
- :column_names => col_names.map(&:to_s)
177
- )
178
- end
179
-
180
- #
181
- ## Column DSL
182
- #
183
-
184
- def column(col_name, kind, opts={})
185
- self << Mystic::SQL::Column.new({
186
- :name => col_name,
187
- :kind => kind.to_sym
188
- }.merge(opts || {}))
189
- end
190
-
191
- def geometry(col_name, kind, srid, opts={})
192
- self << Mystic::SQL::Column.new({
193
- :name => col_name,
194
- :kind => :geometry,
195
- :geom_kind => kind,
196
- :geom_srid => srid
197
- }.merge(opts || {}))
198
- end
199
-
200
- def index(*cols)
201
- opts = cols.delete_at -1 if cols.last.is_a? Hash
202
- opts ||= {}
203
- opts[:columns] = cols
204
- opts[:table_name] = @name
205
- self << Mystic::SQL::Index.new(opts)
206
- end
207
-
208
- def method_missing(meth, *args, &block)
209
- return column args[0], meth.to_s, args[1] if args.count > 0
210
- return @opts[meth] if @opts.member?(meth)
211
- nil
212
- end
213
- end
214
-
215
- class Operation < SQLObject
216
- attr_reader :kind,
217
- :callback
218
-
219
- def initialize(kind, opts={})
220
- @kind = kind
221
- @opts = opts.dup
222
- @callback = @opts.delete :callback
223
- end
224
-
225
- def method_missing(meth, *args, &block)
226
- @opts[meth.to_s.to_sym] rescue nil
227
- end
228
-
229
- def self.method_missing(meth, *args, &block)
230
- new meth, (args[0] || {})
231
- end
232
- end
233
-
234
- class Raw < SQLObject
235
- def initialize(opts)
236
- @sql = opts[:sql]
237
- end
238
-
239
- def to_sql
240
- @sql
241
- end
242
- end
5
+ Error = Class.new StandardError
243
6
  end
244
- end
7
+ end
8
+
9
+ file_folder = File.dirname(File.absolute_path(__FILE__))
10
+ Dir.glob(file_folder + "/sql/**/*.rb", &method(:require))
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Mystic
4
+ module SQL
5
+ class Column
6
+ attr_accessor :name, :kind, :size, :constraints, :geom_kind, :geom_srid
7
+
8
+ def initialize(opts={})
9
+ @name = opts.delete(:name).to_s
10
+ @kind = opts.delete(:kind).to_sym
11
+ @size = opts.delete(:size).to_s if opts.member? :size
12
+ @geom_kind = opts.delete(:geom_kind)
13
+ @geom_srid = opts.delete(:geom_srid).to_i
14
+ @constraints = opts
15
+ end
16
+
17
+ def geospatial?
18
+ @geom_kind && @geom_srid
19
+ end
20
+
21
+ def to_s
22
+ sql = []
23
+ sql << name
24
+ sql << kind.downcase
25
+ sql << "(#{size})" if size && !size.empty? && !geospatial?
26
+ sql << "(#{geom_kind}, #{geom_srid})" if geospatial?
27
+ sql << (constraints[:null] ? "NULL" : "NOT NULL") if constraints.member?(:null)
28
+ sql << "UNIQUE" if constraints[:unique]
29
+ sql << "PRIMARY KEY" if constraints[:primary_key]
30
+ sql << "REFERENCES " + constraints[:references] if constraints.member?(:references)
31
+ sql << "DEFAULT " + constraints[:default] if constraints.member?(:default)
32
+ sql << "CHECK(#{constraints[:check]})" if constraints.member?(:check)
33
+ sql*" "
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Mystic
4
+ module SQL
5
+ class Index
6
+ attr_accessor :name, # Symbol or string
7
+ :table_name, # Symbol or string
8
+ :type, # Symbol
9
+ :unique, # TrueClass/FalseClass
10
+ :columns, # Array of Strings
11
+ :opts # Hash, see below
12
+
13
+ INDEX_TYPES = [
14
+ :btree,
15
+ :hash,
16
+ :gist,
17
+ :spgist,
18
+ :gin
19
+ ].freeze
20
+
21
+ # opts
22
+ # It's a Hash that represents options
23
+ #
24
+ # Key => Value (type)
25
+ # :fillfactor => A value in the range 10..100 (Integer)
26
+ # :fastupdate => true/false (TrueClass/FalseClass)
27
+ # :concurrently => true/false (TrueClass/FalseClass)
28
+ # :tablespace => The name of the desired tablespace (String)
29
+ # :buffering => :on/:off/:auto (Symbol)
30
+ # :concurrently => true/false (TrueClass/FalseClass)
31
+ # :where => The conditions for including entries in your index, same as SELECT * FROM table WHERE ____ (String)
32
+
33
+ def initialize opts={}
34
+ opts.symbolize!
35
+ raise ArgumentError, "Missing table_name." unless opts.member? :table_name
36
+ raise ArgumentError, "Indeces need columns or else what's the point?" unless opts.member? :columns
37
+ @name = opts.delete(:name).to_sym if opts.member? :name
38
+ @table_name = opts.delete(:table_name).to_sym
39
+ @type = (opts.delete(:type) || :btree).to_s.downcase.to_sym
40
+ @unique = opts.delete :unique || false
41
+ @columns = opts.delete(:columns).symbolize rescue []
42
+ @opts = opts
43
+ end
44
+
45
+ # can accept shit other than columns like
46
+ # box(location,location)
47
+ def << col
48
+ case col
49
+ when Column then @columns << col.name.to_s
50
+ when String then @columns << col
51
+ else raise ArgumentError, "Column must be a String or a Mystic::SQL::Column" end
52
+ end
53
+
54
+ def method_missing(meth, *args, &block)
55
+ return @opts[meth] if @opts.member? meth
56
+ nil
57
+ end
58
+
59
+ def to_s
60
+ storage_params = opts.subhash :fillfactor, :buffering, :fastupdate
61
+
62
+ sql = []
63
+ sql << "CREATE"
64
+ sql << "UNIQUE" if unique
65
+ sql << "INDEX"
66
+ sql << "CONCURENTLY" if concurrently
67
+ sql << name unless name.nil?
68
+ sql << "ON #{table_name}"
69
+ sql << "USING #{type}" if INDEX_TYPES.include? type
70
+ sql << "(#{columns.map(&:to_s).join ',' })"
71
+ sql << "WITH (#{storage_params.sqlize})" unless storage_params.empty?
72
+ sql << "TABLESPACE #{tablespace}" unless tablespace.nil?
73
+ sql << "WHERE #{where}" unless where.nil?
74
+ sql*' '
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Mystic
4
+ module SQL
5
+ class Table
6
+ attr_reader :name
7
+ attr_accessor :columns,
8
+ :indeces,
9
+ :operations,
10
+ :opts
11
+
12
+ def self.create opts={}
13
+ new true, opts
14
+ end
15
+
16
+ def self.alter opts={}
17
+ new false, opts
18
+ end
19
+
20
+ def initialize is_create=true, opts={}
21
+ @is_create = is_create
22
+ @opts = opts.symbolize
23
+ @columns = []
24
+ @indeces = []
25
+ @operations = []
26
+
27
+ @name = @opts.delete(:name).to_s
28
+ raise ArgumentError, "Argument 'name' is invalid." if @name.empty?
29
+ end
30
+
31
+ def create?
32
+ @is_create
33
+ end
34
+
35
+ def << obj
36
+ case obj
37
+ when Column then @columns << obj
38
+ when Index then @indeces << obj
39
+ when Operation then @operations << obj
40
+ when String then @operations << obj
41
+ else raise ArgumentError, "Argument is not a Mystic::SQL::Column, Mystic::SQL::Operation, or Mystic::SQL::Index." end
42
+ end
43
+
44
+ def to_s
45
+ raise ArgumentError, "Table cannot have zero columns." if @columns.empty?
46
+ sql = []
47
+
48
+ if create?
49
+ tbl = []
50
+ tbl << "CREATE TABLE #{name} (#{columns.map(&:to_s)*","})"
51
+ tbl << "INHERITS #{opts[:inherits]}" if opts[:inherits]
52
+ tbl << "TABLESPACE #{opts[:tablespace]}" if opts[:tablespace]
53
+ sql << tbl*' '
54
+ else
55
+ sql << "ALTER TABLE #{name} #{columns.map{ |c| "ADD COLUMN #{c.to_s}" }*', ' }"
56
+ end
57
+
58
+ sql.push(*indeces.map(&:to_s)) unless indeces.empty?
59
+ sql.push(*operations.map(&:to_s)) unless operations.empty?
60
+ sql*'; '
61
+ end
62
+
63
+ #
64
+ ## Operation DSL
65
+ #
66
+
67
+ def drop_index idx_name
68
+ raise Mystic::SQL::Error, "Cannot drop an index on a table that doesn't exist." if create?
69
+ self << "DROP INDEX #{idx_name}"
70
+ end
71
+
72
+ def rename_column oldname, newname
73
+ raise Mystic::SQL::Error, "Cannot rename a column on a table that doesn't exist." if create?
74
+ self << "ALTER TABLE #{table_name} RENAME COLUMN #{old_name} TO #{new_name}"
75
+ end
76
+
77
+ def rename newname
78
+ raise Mystic::SQL::Error, "Cannot rename a table that doesn't exist." if create?
79
+ self << "ALTER TABLE #{table_name} RENAME TO #{newname}"
80
+ self.name = newname
81
+ end
82
+
83
+ def drop_columns *col_names
84
+ raise Mystic::SQL::Error, "Cannot drop a column(s) on a table that doesn't exist." if create?
85
+ self << "ALTER TABLE #{table_name} #{col_names.map { |c| "DROP COLUMN #{c.to_s}" }*', ' }"
86
+ end
87
+
88
+ #
89
+ ## Column DSL
90
+ #
91
+
92
+ def column col_name, kind, opts={}
93
+ self << Mystic::SQL::Column.new({
94
+ :name => col_name,
95
+ :kind => kind.to_sym
96
+ }.merge(opts || {}))
97
+ end
98
+
99
+ def geometry col_name, kind, srid, opts={}
100
+ self << Mystic::SQL::Column.new({
101
+ :name => col_name,
102
+ :kind => :geometry,
103
+ :geom_kind => kind,
104
+ :geom_srid => srid
105
+ }.merge(opts || {}))
106
+ end
107
+
108
+ def index *cols
109
+ opts = cols.delete_at -1 if cols.last.is_a? Hash
110
+ opts ||= {}
111
+ opts[:columns] = cols
112
+ opts[:table_name] = @name
113
+ self << Mystic::SQL::Index.new(opts)
114
+ end
115
+
116
+ def method_missing meth, *args, &block
117
+ return column args[0], meth.to_s, args[1] if args.count > 0
118
+ super
119
+ end
120
+ end
121
+ end
122
+ end