skiima 0.1.000 → 0.2.2

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.
Files changed (67) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +11 -3
  4. data/Gemfile +12 -6
  5. data/Guardfile +13 -11
  6. data/LICENSE +20 -0
  7. data/Procfile.example +2 -0
  8. data/README.md +170 -23
  9. data/Rakefile +26 -22
  10. data/lib/skiima.rb +61 -240
  11. data/lib/skiima/config.rb +60 -0
  12. data/lib/skiima/config/struct.rb +87 -0
  13. data/lib/skiima/db/connector.rb +195 -0
  14. data/lib/skiima/db/connector/active_record.rb +11 -0
  15. data/lib/skiima/db/connector/active_record/base_connector.rb +34 -0
  16. data/lib/skiima/db/connector/active_record/mysql2_connector.rb +147 -0
  17. data/lib/skiima/db/connector/active_record/mysql_connector.rb +177 -0
  18. data/lib/skiima/db/connector/active_record/postgresql_connector.rb +39 -0
  19. data/lib/skiima/db/helpers/mysql.rb +230 -0
  20. data/lib/skiima/db/helpers/postgresql.rb +221 -0
  21. data/lib/skiima/db/resolver.rb +62 -0
  22. data/lib/skiima/dependency/reader.rb +55 -0
  23. data/lib/skiima/dependency/script.rb +63 -0
  24. data/lib/skiima/i18n.rb +24 -0
  25. data/lib/skiima/loader.rb +108 -0
  26. data/lib/skiima/locales/en.yml +2 -2
  27. data/lib/skiima/logger.rb +54 -0
  28. data/lib/skiima/railtie.rb +10 -0
  29. data/lib/skiima/railties/skiima.rake +31 -0
  30. data/lib/skiima/version.rb +2 -2
  31. data/skiima.gemspec +5 -5
  32. data/spec/config/{database.yml → database.yml.example} +16 -0
  33. data/spec/config/database.yml.travis +69 -0
  34. data/spec/db/skiima/{depends.yml → dependencies.yml} +7 -2
  35. data/spec/db/skiima/{empty_depends.yml → empty_dependencies.yml} +0 -0
  36. data/spec/db/skiima/init_test_db/database.skiima_test.mysql.current.sql +7 -0
  37. data/spec/db/skiima/init_test_db/database.skiima_test.postgresql.current.sql +7 -0
  38. data/spec/mysql2_spec.rb +61 -12
  39. data/spec/mysql_spec.rb +66 -27
  40. data/spec/postgresql_spec.rb +55 -34
  41. data/spec/shared_examples/config_shared_example.rb +40 -0
  42. data/spec/skiima/config/struct_spec.rb +78 -0
  43. data/spec/skiima/config_spec.rb +6 -0
  44. data/spec/skiima/db/connector/active_record/base_connector_spec.rb +0 -0
  45. data/spec/skiima/db/connector/active_record/mysql2_connector_spec.rb +3 -0
  46. data/spec/skiima/db/connector/active_record/mysql_connector_spec.rb +3 -0
  47. data/spec/skiima/db/connector/active_record/postgresql_connector_spec.rb +7 -0
  48. data/spec/skiima/db/connector_spec.rb +6 -0
  49. data/spec/skiima/db/resolver_spec.rb +54 -0
  50. data/spec/skiima/dependency/reader_spec.rb +52 -0
  51. data/spec/skiima/{dependency_spec.rb → dependency/script_spec.rb} +3 -41
  52. data/spec/skiima/i18n_spec.rb +29 -0
  53. data/spec/skiima/loader_spec.rb +102 -0
  54. data/spec/skiima/logger_spec.rb +0 -0
  55. data/spec/skiima_spec.rb +43 -64
  56. data/spec/spec_helper.rb +38 -4
  57. metadata +144 -100
  58. data/lib/skiima/db_adapters.rb +0 -187
  59. data/lib/skiima/db_adapters/base_mysql_adapter.rb +0 -308
  60. data/lib/skiima/db_adapters/mysql2_adapter.rb +0 -114
  61. data/lib/skiima/db_adapters/mysql_adapter.rb +0 -287
  62. data/lib/skiima/db_adapters/postgresql_adapter.rb +0 -509
  63. data/lib/skiima/dependency.rb +0 -84
  64. data/lib/skiima_helpers.rb +0 -49
  65. data/spec/skiima/db_adapters/mysql_adapter_spec.rb +0 -38
  66. data/spec/skiima/db_adapters/postgresql_adapter_spec.rb +0 -20
  67. data/spec/skiima/db_adapters_spec.rb +0 -31
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ require 'skiima/db/helpers/postgresql' unless defined? Skiima::Db::Helpers::Postgresql
3
+ require 'skiima/db/connector/active_record/base_connector' unless defined? Skiima::Db::Connector::ActiveRecord::BaseConnector
4
+ require 'active_record/connection_adapters/postgresql_adapter' unless defined? ActiveRecord::ConnectionAdapters::PostreSQLAdapter
5
+
6
+ module Skiima
7
+ module Db
8
+ module Connector
9
+ module ActiveRecord
10
+
11
+ class PostgresqlConnector < Skiima::Db::Connector::ActiveRecord::BaseConnector
12
+ delegate [:local_tz, :column_names, :check_psql_version,
13
+ :table_exists?, :index_exists?, :rule_exists?,
14
+ :view_exists?, :schema_exists?] => :adapter
15
+
16
+ def initialize(adapter, logger, config = {})
17
+ super
18
+ check_psql_version
19
+ end
20
+
21
+ class << self
22
+ delegate postgresql_connection: :active_record_resolver_klass
23
+
24
+ def create_adapter(config, logger, pool)
25
+ case ::ActiveRecord::VERSION::MAJOR
26
+ when 3,4 then send('postgresql_connection', config)
27
+ end
28
+ end
29
+
30
+ def helpers_module
31
+ Skiima::Db::Helpers::Postgresql
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,230 @@
1
+ module Skiima
2
+ module Db
3
+ module Helpers
4
+ module Mysql
5
+ #attr_accessor :version
6
+
7
+ def supported_objects
8
+ [:database, :table, :view, :index, :proc]
9
+ end
10
+
11
+ def execute(sql, name = nil)
12
+ # relying on formatting inside the file is precisely what i wanted to avoid...
13
+ results = sql.split(/^--={4,}/).map do |line|
14
+ super(line)
15
+ end
16
+
17
+ results.first
18
+ end
19
+
20
+ def exec_query(sql, name = 'SQL')
21
+ log(sql, name) do
22
+ exec_stmt(sql, name) do |cols, stmt|
23
+ stmt.to_a
24
+ end
25
+ end
26
+ end
27
+
28
+ #override?
29
+ def tables(name = nil, database = nil, like = nil)
30
+ sql = "SHOW FULL TABLES "
31
+ sql << "IN #{database} " if database
32
+ sql << "WHERE table_type = 'BASE TABLE' "
33
+ sql << "LIKE '#{like}' " if like
34
+
35
+ execute_and_free(sql, 'SCHEMA') do |result|
36
+ result.collect { |field| field.first }
37
+ end
38
+ end
39
+
40
+ def views(name = nil, database = nil, like = nil)
41
+ sql = "SHOW FULL TABLES "
42
+ sql << "IN #{database} " if database
43
+ sql << "WHERE table_type = 'VIEW' "
44
+ sql << "LIKE '#{like}' " if like
45
+
46
+ execute_and_free(sql, 'SCHEMA') do |result|
47
+ result.collect { |field| field.first }
48
+ end
49
+ end
50
+
51
+ def indexes(name = nil, database = nil, table = nil)
52
+ sql = "SHOW INDEX "
53
+ sql << "IN #{table} "
54
+ sql << "IN #{database} " if database
55
+ sql << "WHERE key_name = '#{name}'" if name
56
+
57
+ execute_and_free(sql, 'SCHEMA') do |result|
58
+ result.collect { |field| field[2] }
59
+ end
60
+ end
61
+
62
+ def procs(name = nil, database = nil, like = nil)
63
+ sql = "SELECT r.routine_name "
64
+ sql << "FROM information_schema.routines r "
65
+ sql << "WHERE r.routine_type = 'PROCEDURE' "
66
+ sql << "AND r.routine_name LIKE '#{like}' " if like
67
+ sql << "AND r.routine_schema = #{database} " if database
68
+
69
+ execute_and_free(sql, 'SCHEMA') do |result|
70
+ result.collect { |field| field.first }
71
+ end
72
+ end
73
+
74
+ # needed?
75
+ #def column_definitions(table_name)
76
+ # # "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
77
+ #end
78
+
79
+ # keep?
80
+ #def column_names(table_name)
81
+ # sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
82
+ # execute_and_free(sql, 'SCHEMA') do |result|
83
+ # result.collect { |field| field.first }
84
+ # end
85
+ #end
86
+
87
+ def column_names(table_name)
88
+ columns(table_name).map(&:name)
89
+ end
90
+
91
+ # schema matchers
92
+ def object_exists?(type, name, opts = {})
93
+ send("#{type}_exists?", name, opts) if supported_objects.include? type.to_sym
94
+ end
95
+
96
+ def database_exists?(name)
97
+ #stub
98
+ end
99
+
100
+ def table_exists?(name)
101
+ return false unless name
102
+ return true if tables(nil, nil, name).any?
103
+
104
+ name = name.to_s
105
+ schema, table = name.split('.', 2)
106
+
107
+ unless table # A table was provided without a schema
108
+ table = schema
109
+ schema = nil
110
+ end
111
+
112
+ tables(nil, schema, table).any?
113
+ end
114
+
115
+ def view_exists?(name)
116
+ return false unless name
117
+ return true if views(nil, nil, name).any?
118
+
119
+ name = name.to_s
120
+ schema, view = name.split('.', 2)
121
+
122
+ unless view # A table was provided without a schema
123
+ view = schema
124
+ schema = nil
125
+ end
126
+
127
+ views(nil, schema, view).any?
128
+ end
129
+
130
+ def index_exists?(name, opts = {})
131
+ target = opts[:attr] ? opts[:attr][0] : nil
132
+ raise "requires target object" unless target
133
+
134
+ return false unless table_exists?(target) #mysql blows up when table doesn't exist
135
+ return false unless name
136
+ return true if indexes(name, nil, target).any?
137
+
138
+ name = name.to_s
139
+ schema, target = name.split('.', 2)
140
+
141
+ unless target # A table was provided without a schema
142
+ target = schema
143
+ schema = nil
144
+ end
145
+
146
+ indexes(name, schema, target).any?
147
+ end
148
+
149
+ def proc_exists?(name, opts = {})
150
+ return false unless name
151
+ return true if procs(nil, nil, name).any?
152
+
153
+ name = name.to_s
154
+ schema, proc = name.split('.', 2)
155
+
156
+ unless proc # A table was provided without a schema
157
+ proc = schema
158
+ schema = nil
159
+ end
160
+
161
+ procs(name, schema, proc).any?
162
+ end
163
+
164
+ # queries
165
+ def drop(type, name, opts = {})
166
+ send("drop_#{type}", name, opts) if supported_objects.include? type.to_sym
167
+ end
168
+
169
+ def drop_database(name, opts = {})
170
+ "DROP DATABASE IF EXISTS #{name}"
171
+ end
172
+
173
+ def drop_table(name, opts = {})
174
+ "DROP TABLE IF EXISTS #{name}"
175
+ end
176
+
177
+ def drop_view(name, opts = {})
178
+ "DROP VIEW IF EXISTS #{name}"
179
+ end
180
+
181
+ def drop_proc(name, opts = {})
182
+ "DROP PROCEDURE IF EXISTS #{name}"
183
+ end
184
+
185
+ def drop_index(name, opts = {})
186
+ target = opts[:attr].first if opts[:attr]
187
+ raise "requires target object" unless target
188
+
189
+ "DROP INDEX #{name} ON #{target}"
190
+ end
191
+
192
+ protected
193
+
194
+ #def translate_exception(exception, message)
195
+ # exception
196
+ # # case error_number(exception)
197
+ # # when 1062
198
+ # # RecordNotUnique.new(message, exception)
199
+ # # when 1452
200
+ # # InvalidForeignKey.new(message, exception)
201
+ # # else
202
+ # # super
203
+ # # end
204
+ #end
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ ## encoding: utf-8
211
+ #module Skiima
212
+ # module DbAdapters
213
+ # class BaseMysqlAdapter < Base
214
+ # attr_accessor :version
215
+ #
216
+ # LOST_CONNECTION_ERROR_MESSAGES = [
217
+ # "Server shutdown in progress",
218
+ # "Broken pipe",
219
+ # "Lost connection to MySQL server during query",
220
+ # "MySQL server has gone away" ]
221
+ #
222
+ # # FIXME: Make the first parameter more similar for the two adapters
223
+ # def initialize(connection, logger, connection_options, config)
224
+ # super(connection, logger)
225
+ # @connection_options, @config = connection_options, config
226
+ # @quoted_column_names, @quoted_table_names = {}, {}
227
+ # end
228
+ # end
229
+ # end
230
+ #end
@@ -0,0 +1,221 @@
1
+ module Skiima
2
+ module Db
3
+ module Helpers
4
+ module Postgresql
5
+ attr_accessor :local_tz
6
+ attr_accessor :version
7
+
8
+ def execute(sql, name = nil)
9
+ # relying on formatting inside the file is precisely what i wanted to avoid...
10
+ results = sql.split(/^--={4,}/).map do |line|
11
+ super(line)
12
+ end
13
+
14
+ results.first
15
+ end
16
+
17
+ # skiima
18
+ def supported_objects
19
+ [:database, :schema, :table, :view, :rule, :index]
20
+ end
21
+
22
+ def check_psql_version
23
+ @version = postgresql_version
24
+ if @version < 80200
25
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
26
+ end
27
+ end
28
+
29
+ # schema matchers
30
+ def object_exists?(type, name, opts = {})
31
+ send("#{type}_exists?", name, opts) if supported_objects.include? type.to_sym
32
+ end
33
+
34
+ def database_exists?(name, opts = {})
35
+ query(Skiima.interpolate_sql('&', <<-SQL, { :database => name }))[0][0].to_i > 0
36
+ SELECT COUNT(*)
37
+ FROM pg_databases pdb
38
+ WHERE pdb.datname = '&database'
39
+ SQL
40
+ end
41
+
42
+ def schema_exists?(name, opts = {})
43
+ query(Skiima.interpolate_sql('&', <<-SQL, { :schema => name }))[0][0].to_i > 0
44
+ SELECT COUNT(*)
45
+ FROM pg_namespace
46
+ WHERE nspname = '&schema'
47
+ SQL
48
+ end
49
+
50
+ def table_exists?(name, opts = {})
51
+ schema, table = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(name.to_s)
52
+ vars = { :table => table,
53
+ :schema => ((schema && !schema.empty?) ? "'#{schema}'" : "ANY (current_schemas(false))") }
54
+
55
+ vars.inspect
56
+
57
+ query(Skiima.interpolate_sql('&', <<-SQL, vars))[0][0].to_i > 0
58
+ SELECT COUNT(*)
59
+ FROM pg_class c
60
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
61
+ WHERE c.relkind in ('r')
62
+ AND c.relname = '&table'
63
+ AND n.nspname = &schema
64
+ SQL
65
+ end
66
+
67
+ def view_exists?(name, opts = {})
68
+ schema, view = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(name.to_s)
69
+ vars = { :view => view,
70
+ :schema => ((schema && !schema.empty?) ? "'#{schema}'" : "ANY (current_schemas(false))") }
71
+
72
+ query(Skiima.interpolate_sql('&', <<-SQL, vars))[0][0].to_i > 0
73
+ SELECT COUNT(*)
74
+ FROM pg_class c
75
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
76
+ WHERE c.relkind in ('v')
77
+ AND c.relname = '&view'
78
+ AND n.nspname = &schema
79
+ SQL
80
+ end
81
+
82
+ def rule_exists?(name, opts = {})
83
+ target = opts[:attr] ? opts[:attr][0] : nil
84
+ raise "requires target object" unless target
85
+ schema, rule = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(name.to_s)
86
+ vars = { :rule => rule,
87
+ :target => target,
88
+ :schema => ((schema && !schema.empty?) ? "'#{schema}'" : "ANY (current_schemas(false))") }
89
+
90
+ query(Skiima.interpolate_sql('&', <<-SQL, vars))[0][0].to_i > 0
91
+ SELECT COUNT(*)
92
+ FROM pg_rules pgr
93
+ WHERE pgr.rulename = '&rule'
94
+ AND pgr.tablename = '&target'
95
+ AND pgr.schemaname = &schema
96
+ SQL
97
+ end
98
+
99
+ def index_exists?(name, opts = {})
100
+ target = opts[:attr] ? opts[:attr][0] : nil
101
+ raise "requires target object" unless target
102
+ schema, index = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(name.to_s)
103
+ vars = { :index => index,
104
+ :target => target,
105
+ :schema => ((schema && !schema.empty?) ? "'#{schema}'" : "ANY (current_schemas(false))") }
106
+
107
+ query(Skiima.interpolate_sql('&', <<-SQL, vars))[0][0].to_i > 0
108
+ SELECT COUNT(*)
109
+ FROM pg_indexes pgr
110
+ WHERE pgr.indexname = '&index'
111
+ AND pgr.tablename = '&target'
112
+ AND pgr.schemaname = &schema
113
+ SQL
114
+ end
115
+
116
+ # queries
117
+ def drop(type, name, opts = {})
118
+ send("drop_#{type}", name, opts) if supported_objects.include? type.to_sym
119
+ end
120
+
121
+ def drop_database(name, opts = {})
122
+ "DROP DATABASE IF EXISTS #{name}"
123
+ end
124
+
125
+ def drop_schema(name, opts = {})
126
+ "DROP SCHEMA IF EXISTS #{name}"
127
+ end
128
+
129
+ def drop_table(name, opts = {})
130
+ "DROP TABLE IF EXISTS #{name}"
131
+ end
132
+
133
+ def drop_view(name, opts = {})
134
+ "DROP VIEW IF EXISTS #{name}"
135
+ end
136
+
137
+ def drop_rule(name, opts = {})
138
+ target = opts[:attr].first if opts[:attr]
139
+ raise "requires target object" unless target
140
+
141
+ "DROP RULE IF EXISTS #{name} ON #{target}"
142
+ end
143
+
144
+ def drop_index(name, opts = {})
145
+ "DROP INDEX IF EXISTS #{name}"
146
+ end
147
+
148
+ # in original? why?
149
+ #necessary 2 override?
150
+ #def translate_exception(e, message)
151
+ # e
152
+ # # case exception.message
153
+ # # when /duplicate key value violates unique constraint/
154
+ # # RecordNotUnique.new(message, exception)
155
+ # # when /violates foreign key constraint/
156
+ # # InvalidForeignKey.new(message, exception)
157
+ # # else
158
+ # # super
159
+ # # end
160
+ #end
161
+
162
+ # necessary 2 override?
163
+ #class PgColumn
164
+ # attr_accessor :name, :deafult, :type, :null
165
+ # def initialize(name, default = nil, type = nil, null = true)
166
+ # @name, @default, @type, @null = name, default, type, null
167
+ # end
168
+ #
169
+ # def to_s
170
+ # # to be implemented
171
+ # end
172
+ #end
173
+ def column_names(table_name)
174
+ columns(table_name).map(&:name)
175
+ end
176
+
177
+ # necessary 2 override?
178
+ ## Close then reopen the connection.
179
+ #def reconnect!
180
+ # clear_cache!
181
+ # @connection.reset
182
+ # configure_connection
183
+ #end
184
+
185
+ # necessary 2 override?
186
+ ## Is this connection alive and ready for queries?
187
+ #def active?
188
+ # @connection.status == PGconn::CONNECTION_OK
189
+ #rescue PGError
190
+ # false
191
+ #end
192
+
193
+ protected
194
+
195
+ def get_timezone
196
+ execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
197
+ end
198
+
199
+ private
200
+
201
+ #necessary 2 override?
202
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
203
+ # This is called by #connect and should not be called manually.
204
+ #def configure_connection
205
+ # if @config[:encoding]
206
+ # @connection.set_client_encoding(@config[:encoding])
207
+ # end
208
+ # self.client_min_messages = @config[:min_messages] if @config[:min_messages]
209
+ # self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
210
+ #
211
+ # # Use standard-conforming strings if available so we don't have to do the E'...' dance.
212
+ # set_standard_conforming_strings
213
+ #
214
+ # #configure the connection to return TIMESTAMP WITH ZONE types in UTC.
215
+ # execute("SET time zone '#{@local_tz}'", 'SCHEMA') if @local_tz
216
+ #end
217
+
218
+ end
219
+ end
220
+ end
221
+ end