dohmysql 0.1.0

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 (46) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/bin/makedb +28 -0
  3. data/lib/doh/mysql/abstract_row.rb +80 -0
  4. data/lib/doh/mysql/activate.rb +31 -0
  5. data/lib/doh/mysql/cache_connector.rb +54 -0
  6. data/lib/doh/mysql/connector_instance.rb +79 -0
  7. data/lib/doh/mysql/connector_util.rb +27 -0
  8. data/lib/doh/mysql/convert.rb +18 -0
  9. data/lib/doh/mysql/current_date.rb +22 -0
  10. data/lib/doh/mysql/database_creator.rb +101 -0
  11. data/lib/doh/mysql/db_date.rb +28 -0
  12. data/lib/doh/mysql/default_type_guesser.rb +37 -0
  13. data/lib/doh/mysql/error.rb +7 -0
  14. data/lib/doh/mysql/handle.rb +218 -0
  15. data/lib/doh/mysql/hash_row.rb +13 -0
  16. data/lib/doh/mysql/load_sql.rb +26 -0
  17. data/lib/doh/mysql/metadata_util.rb +73 -0
  18. data/lib/doh/mysql/parse.rb +36 -0
  19. data/lib/doh/mysql/raw_row_builder.rb +15 -0
  20. data/lib/doh/mysql/readonly_row.rb +26 -0
  21. data/lib/doh/mysql/require_dbtypes.rb +8 -0
  22. data/lib/doh/mysql/smart_row.rb +156 -0
  23. data/lib/doh/mysql/to_sql.rb +65 -0
  24. data/lib/doh/mysql/typed_row_builder.rb +28 -0
  25. data/lib/doh/mysql/types.rb +33 -0
  26. data/lib/doh/mysql/unquoted.rb +17 -0
  27. data/lib/doh/mysql/version.rb +102 -0
  28. data/lib/doh/mysql/virtual.rb +17 -0
  29. data/lib/doh/mysql/writable_row.rb +59 -0
  30. data/lib/doh/mysql.rb +7 -0
  31. data/test/cache_connector.dt.rb +41 -0
  32. data/test/connector.yml +4 -0
  33. data/test/connector.yml.tmpl +4 -0
  34. data/test/connector_instance.dt.rb +32 -0
  35. data/test/convert.dt.rb +45 -0
  36. data/test/db_unit_test.rb +10 -0
  37. data/test/handle.dt.rb +112 -0
  38. data/test/metadata_util.dt.rb +53 -0
  39. data/test/parse.dt.rb +39 -0
  40. data/test/readonly_row.dt.rb +85 -0
  41. data/test/smart_row.dt.rb +21 -0
  42. data/test/to_sql.dt.rb +19 -0
  43. data/test/types.dt.rb +32 -0
  44. data/test/unquoted.dt.rb +16 -0
  45. data/test/writable_row.dt.rb +21 -0
  46. metadata +118 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Makani Mason, Kem Mason
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/makedb ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ require 'doh/app/pwd'
3
+ require 'doh/options'
4
+ require 'doh/mysql/database_creator'
5
+
6
+ opts = Doh::Options.new(
7
+ {'drop_first' => [false, "-z", "--drop_first", "if true, will drop the database or tables before creating"] \
8
+ ,'database' => [Doh::config['primary_database'], "-d", "--database <database>", "name of the source database -- defaults to config['primary_database'], currently '#{Doh::config['primary_database']}'"] \
9
+ ,'all' => [false, "-a", "--all", "create all databases"] \
10
+ ,'target' => [Doh::config['target_database'], "-g", "--target <database>", "name of the target database -- defaults to same as source"] \
11
+ ,'tables' => [nil, "-t", "--tables <tables>", "comma-delimited list of tables to create; if specified, no databases are dropped or created"]
12
+ })
13
+
14
+ if opts.database.to_s.empty? && !opts.all
15
+ puts "You must specify a database (either here with -d or in your config) or all (with -a)"
16
+ exit 1
17
+ end
18
+
19
+ db_creator = DohDb::DatabaseCreator.new
20
+ if opts.tables
21
+ db_creator.create_tables(opts.database, opts.drop_first, opts.tables.split(/,/))
22
+ elsif opts.all
23
+ db_creator.create_all_databases(opts.drop_first)
24
+ elsif opts.target
25
+ db_creator.create_database_copy(opts.target, opts.database, opts.drop_first)
26
+ else
27
+ db_creator.create_database(opts.database, opts.drop_first)
28
+ end
@@ -0,0 +1,80 @@
1
+ require 'doh/core_ext/class/force_deep_copy'
2
+
3
+ module DohDb
4
+
5
+ class AbstractRow
6
+ attr_reader :keys, :values
7
+ force_deep_copy :keys, :values
8
+
9
+ def at(index)
10
+ @values.at(index)
11
+ end
12
+
13
+ def get(key)
14
+ index = @keys.index(key)
15
+ if index
16
+ @values.at(index)
17
+ else
18
+ nil
19
+ end
20
+ end
21
+ alias [] get
22
+
23
+ def key?(key)
24
+ !@keys.index(key).nil?
25
+ end
26
+
27
+ def to_a
28
+ retval = []
29
+ @keys.size.times {|index| retval.push([@keys[index], @values[index]])}
30
+ retval
31
+ end
32
+
33
+ def to_h
34
+ retval = {}
35
+ @keys.each_with_index {|key, index| retval[key] = @values.at(index)}
36
+ retval
37
+ end
38
+
39
+ def inspect
40
+ ary = []
41
+ @keys.size.times {|index| ary.push([@keys[index], @values[index]])}
42
+ ary.inspect
43
+ end
44
+
45
+ def each_pair
46
+ @keys.size.times do |index|
47
+ yield(@keys.at(index), @values.at(index))
48
+ end
49
+ end
50
+
51
+ def size
52
+ @keys.size
53
+ end
54
+
55
+ def empty_field?(key)
56
+ return true if !key?(key)
57
+ val = get(key)
58
+ return val.nil? || (val.respond_to?(:empty?) && val.empty?)
59
+ end
60
+
61
+ def record_id
62
+ get('id')
63
+ end
64
+
65
+ protected
66
+ def parse_initialize_args(*args)
67
+ if args.empty?
68
+ [[], []]
69
+ elsif args[0].is_a?(Array)
70
+ raise "first arg is array, second must be also" unless args[1].is_a?(Array)
71
+ raise "first two args are arrays, must be of the same size" unless args[0].size == args[1].size
72
+ args
73
+ else
74
+ hash = args[0]
75
+ [hash.keys, hash.values] + args[1..-1]
76
+ end
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,31 @@
1
+ require 'doh/mysql/cache_connector'
2
+ require 'doh/mysql/connector_instance'
3
+ require 'doh/mysql/handle'
4
+ require 'doh/mysql/to_sql'
5
+ require 'doh/mysql/db_date'
6
+ require 'doh/mysql/unquoted'
7
+ require 'doh/config'
8
+
9
+ module DohDb
10
+
11
+ def self.activate
12
+ root_cfg = Doh::config
13
+ return unless root_cfg.fetch('enable_database', true)
14
+ return unless root_cfg.key?('database')
15
+ begin
16
+ require 'mysql'
17
+ rescue LoadError => e
18
+ return
19
+ end
20
+ db_cfg = root_cfg['database']
21
+ require 'doh/mysql'
22
+ require 'doh/mysql/require_dbtypes'
23
+ conn = DohDb::CacheConnector.new(db_cfg['host'], db_cfg['username'], db_cfg['password'], db_cfg['database'] || root_cfg['primary_database'])
24
+ conn.port = db_cfg['port']
25
+ conn.timeout = db_cfg['timeout'].to_i if db_cfg['timeout']
26
+ conn.row_builder = db_cfg['row_builder'] || root_cfg['row_builder']
27
+ DohDb::set_connector_instance(conn)
28
+ DohDb::require_dbtypes
29
+ end
30
+
31
+ end
@@ -0,0 +1,54 @@
1
+ require 'doh/mysql/handle'
2
+ require 'doh/mysql/typed_row_builder'
3
+
4
+ module DohDb
5
+
6
+ class CacheConnector
7
+ attr_accessor :host, :username, :password, :database, :row_builder, :timeout, :port
8
+
9
+ def initialize(host = nil, username = nil, password = nil, database = nil, row_builder = nil)
10
+ @host = host
11
+ @username = username
12
+ @password = password
13
+ @database = database
14
+ @timeout = 1800
15
+ @port = nil
16
+ @row_builder = row_builder || TypedRowBuilder.new
17
+ end
18
+
19
+ def request_handle(database = nil)
20
+ if @handle
21
+ close_handle("handle was unused for too long") if passed_timeout?
22
+ @handle = nil if @handle && @handle.closed?
23
+ end
24
+ @last_used = Time.now
25
+ @handle ||= get_new_handle(database)
26
+ end
27
+
28
+ def reset
29
+ close_handle("reset")
30
+ end
31
+
32
+ private
33
+ def close_handle(msg)
34
+ return unless @handle
35
+ dohlog.debug("closing previous database connection - #{msg}")
36
+ @handle.close
37
+ @handle = nil
38
+ end
39
+
40
+ def get_new_handle(database = nil)
41
+ database ||= @database
42
+ dbmsg = database.to_s.strip.empty? ? 'no default database' : "database #{database}"
43
+ dohlog.info("connecting to #@host port #@port as username #@username, #{dbmsg}")
44
+ mysqlh = Mysql.connect(@host, @username, @password, database, @port, nil, Mysql::CLIENT_MULTI_STATEMENTS)
45
+ mysqlh.query_with_result = false
46
+ Handle.new(mysqlh, @row_builder)
47
+ end
48
+
49
+ def passed_timeout?
50
+ Time.now > @last_used + @timeout
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,79 @@
1
+ module DohDb
2
+
3
+ def self.set_connector_instance(conn)
4
+ @@connector_instance = conn
5
+ end
6
+
7
+ def self.connector_instance
8
+ @@connector_instance
9
+ end
10
+
11
+ def self.request_handle
12
+ connector_instance.request_handle
13
+ end
14
+
15
+ def self.query(statement)
16
+ request_handle.query(statement)
17
+ end
18
+
19
+ def self.update(statement)
20
+ request_handle.update(statement)
21
+ end
22
+
23
+ def self.update_row(statement)
24
+ request_handle.update_row(statement)
25
+ end
26
+
27
+ def self.update_hash(hash, table, primary_key_value, primary_key_name)
28
+ request_handle.update_hash(hash, table, primary_key_value, primary_key_name)
29
+ end
30
+
31
+ def self.insert(statement)
32
+ request_handle.insert(statement)
33
+ end
34
+
35
+ def self.insert_hash(hash, table, ignore = nil)
36
+ request_handle.insert_hash(hash, table, ignore)
37
+ end
38
+
39
+ def self.replace_hash(hash, table)
40
+ request_handle.replace_hash(hash, table)
41
+ end
42
+
43
+ def self.select(statement, row_builder = nil)
44
+ request_handle.select(statement, row_builder)
45
+ end
46
+
47
+ def self.select_row(statement, row_builder = nil)
48
+ request_handle.select_row(statement, row_builder)
49
+ end
50
+
51
+ def self.select_optional_row(statement, row_builder = nil)
52
+ request_handle.select_optional_row(statement, row_builder)
53
+ end
54
+
55
+ def self.select_field(statement, row_builder = nil)
56
+ request_handle.select_field(statement, row_builder)
57
+ end
58
+
59
+ def self.select_optional_field(statement, row_builder = nil)
60
+ request_handle.select_optional_field(statement, row_builder)
61
+ end
62
+
63
+ def self.select_transpose(statement, row_builder = nil)
64
+ request_handle.select_transpose(statement, row_builder)
65
+ end
66
+
67
+ def self.select_values(statement, row_builder = nil)
68
+ request_handle.select_values(statement, row_builder)
69
+ end
70
+
71
+ def self.select_list(statement, row_builder = nil)
72
+ request_handle.select_list(statement, row_builder)
73
+ end
74
+
75
+ def self.multi_select(statements)
76
+ request_handle.multi_select(statements)
77
+ end
78
+
79
+ end
@@ -0,0 +1,27 @@
1
+ module DohDb
2
+
3
+ def self.create_and_connect(connector, new_default_database = nil, drop_first = true)
4
+ connector.reset
5
+ connector.database = new_default_database if new_default_database
6
+ dbh = connector.request_handle('')
7
+ dbh.query("DROP DATABASE IF EXISTS #{connector.database}") if drop_first
8
+ dbh.query("CREATE DATABASE IF NOT EXISTS #{connector.database}")
9
+ dbh.query("USE #{connector.database}")
10
+ dbh
11
+ end
12
+
13
+ def self.drop_create_and_connect(connector, new_default_database = nil)
14
+ create_and_connect(connector, new_default_database, true)
15
+ end
16
+
17
+ def self.reconfigure_connector(cfg, connector = nil)
18
+ connector ||= DohDb::connector_instance
19
+ connector.reset
20
+ connector.host = cfg['host'] if cfg.key?('host')
21
+ connector.username = cfg['username'] if cfg.key?('username')
22
+ connector.password = cfg['password'] if cfg.key?('password')
23
+ connector.database = cfg['database'] if cfg.key?('database')
24
+ connector.port = cfg['port'] if cfg.key?('port')
25
+ end
26
+
27
+ end
@@ -0,0 +1,18 @@
1
+ require 'doh/mysql/metadata_util'
2
+ require 'doh/mysql/error'
3
+
4
+ module DohDb
5
+
6
+ def self.convert(table, column, value)
7
+ info = column_info(table)[column]
8
+ # raise UnknownColumn, "#{table}.#{column}" if info.nil?
9
+ return value if info.nil?
10
+ if value.nil?
11
+ raise CannotBeNull, "#{table}.#{column}" if info['is_nullable'] == 'NO'
12
+ return nil
13
+ end
14
+ return nil if value.is_a?(String) && value.empty? && info['is_nullable'] == 'YES'
15
+ value
16
+ end
17
+
18
+ end
@@ -0,0 +1,22 @@
1
+ require 'doh/current_date'
2
+ require 'doh/mysql/db_date'
3
+ require 'doh/mysql/parse'
4
+
5
+ module DohDb
6
+
7
+ def self.current_date_db
8
+ Doh::current_date(DohDb::today)
9
+ end
10
+
11
+ def self.current_datetime_db
12
+ Doh::current_datetime(DohDb::now)
13
+ end
14
+
15
+ def self.server_datetime
16
+ retval = DohDb::select_field("SELECT #{DohDb::current_datetime_db.to_sql}")
17
+ # if there is a fake datetime right now, will need to parse it
18
+ retval = DohDb::parse_datetime(retval) if retval.is_a?(String)
19
+ retval
20
+ end
21
+
22
+ end
@@ -0,0 +1,101 @@
1
+ require 'doh/core_ext/dir'
2
+ require 'doh/mysql/handle'
3
+ require 'doh/mysql/load_sql'
4
+ require 'doh/mysql/version'
5
+ require 'doh/mysql/types'
6
+
7
+ module DohDb
8
+
9
+ class DatabaseCreator
10
+ def initialize(data_directory = nil, connector = nil)
11
+ @data_directory = data_directory || File.join(Doh::root, 'database')
12
+ @connector = connector || DohDb::connector_instance
13
+ @include_scripts = true
14
+ end
15
+
16
+ def create_database(dbname, drop_first = false)
17
+ create_one_database(get_nodb_handle, dbname, dbname, drop_first)
18
+ end
19
+
20
+ def create_database_copy(dest_db, source_db, drop_first = false)
21
+ create_one_database(get_nodb_handle, dest_db, source_db, drop_first)
22
+ DohDb::link_database_types(dest_db, source_db)
23
+ end
24
+
25
+ def create_all_databases(drop_first = false)
26
+ dbh = get_nodb_handle
27
+ Dir.directories(@data_directory).each {|elem| create_one_database(dbh, elem, elem, drop_first)}
28
+ end
29
+
30
+ def create_tables(database, drop_first, *table_and_view_names)
31
+ @connector.database = database
32
+ views, tables = table_and_view_names.flatten.sort.partition {|name| File.exist?(sql_filename(database, 'views', name))}
33
+ tables.each {|name| create_base_table(database, name, drop_first)}
34
+ views.each {|name| create_view(database, name, drop_first)}
35
+ end
36
+
37
+ def exclude_scripts
38
+ @include_scripts = false
39
+ self
40
+ end
41
+
42
+ private
43
+ def get_nodb_handle
44
+ @connector.reset
45
+ @connector.database = ''
46
+ @connector.request_handle
47
+ end
48
+
49
+ def sql_filename(database, subdir, name)
50
+ File.join(@data_directory, database, subdir, name) + '.sql'
51
+ end
52
+
53
+ def find_files(source_db, subdir, ext = '.sql')
54
+ path = File.join(@data_directory, source_db, subdir)
55
+ return [] unless File.exist?(path)
56
+ Dir.entries(path).find_all {|entry| entry.end_with?(ext)}.sort.collect {|elem| File.join(path, elem)}
57
+ end
58
+
59
+ def view_files(source_db)
60
+ path = File.join(@data_directory, source_db, 'views')
61
+ return [] unless File.exist?(path)
62
+ ordered_filenames = YAML.load_file(File.join(path, 'order.yml')).collect {|uqfn| File.join(path, uqfn) + '.sql'}
63
+ ordered_filenames + (find_files(source_db, 'views') - ordered_filenames)
64
+ end
65
+
66
+ def create_base_table(database, table_name, drop_first)
67
+ DohDb::query("DROP TABLE IF EXISTS #{table_name}") if drop_first
68
+ files = [sql_filename(database, 'tables', table_name)]
69
+ inserts_file = sql_filename(database, 'insert_sql', table_name)
70
+ files.push(inserts_file) if File.exist?(inserts_file)
71
+ DohDb::load_sql_connector(files, DohDb::connector_instance)
72
+ end
73
+
74
+ def create_view(database, view_name, drop_first)
75
+ DohDb::query("DROP VIEW IF EXISTS #{view_name}") if drop_first
76
+ DohDb::load_sql_connector([sql_filename(database, 'views', view_name)], DohDb::connector_instance)
77
+ end
78
+
79
+ def create_one_database(dbh, dest_db, source_db, drop_first)
80
+ dohlog.info("creating database " + dest_db + " from source files at " + File.join(@data_directory, source_db))
81
+ dbh.query("DROP DATABASE IF EXISTS " + dest_db) if drop_first
82
+
83
+ dbh.query("CREATE DATABASE " + dest_db)
84
+ dbh.query("USE " + dest_db)
85
+ dbh.query("CREATE TABLE version (version INT UNSIGNED NOT NULL) ENGINE=MyISAM")
86
+ dbver = DohDb::latest_database_version(source_db)
87
+ dbh.query("INSERT INTO version VALUES (#{dbver})")
88
+
89
+ @connector.database = dest_db
90
+
91
+ files = find_files(source_db, 'tables') + find_files(source_db, 'insert_sql') + view_files(source_db)
92
+ DohDb::load_sql_connector(files, @connector, dest_db)
93
+ return unless @include_scripts
94
+ find_files(source_db, 'insert_scripts', '.rb').each do |filename|
95
+ dohlog.info("loading file: #{filename}")
96
+ load(filename)
97
+ end
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,28 @@
1
+ require 'date'
2
+ require 'doh/mysql/to_sql'
3
+
4
+ module DohDb
5
+
6
+ class DateToday < Date
7
+ def to_sql
8
+ 'CURDATE()'
9
+ end
10
+ end
11
+
12
+ class DateTimeNow < DateTime
13
+ def to_sql
14
+ 'NOW()'
15
+ end
16
+ end
17
+
18
+ def self.today
19
+ day = Date.today
20
+ DateToday.new(day.year, day.month, day.mday)
21
+ end
22
+
23
+ def self.now
24
+ dt = DateTime.zow
25
+ DateTimeNow.new(dt.year, dt.month, dt.mday, dt.hour, dt.min, dt.sec, dt.zone)
26
+ end
27
+
28
+ end
@@ -0,0 +1,37 @@
1
+ require 'mysql'
2
+ require 'doh/mysql/parse'
3
+ require 'doh/to_display'
4
+ require 'doh/mysql/types'
5
+ require 'doh/core_ext/string'
6
+
7
+ module DohDb
8
+
9
+ class DefaultTypeGuesser
10
+ # for compatibility with older mysql gems
11
+ if !MysqlField.const_defined?('TYPE_NEWDECIMAL')
12
+ MysqlField::TYPE_NEWDECIMAL = 246
13
+ end
14
+ DECIMAL_TYPES = [MysqlField::TYPE_DECIMAL, MysqlField::TYPE_NEWDECIMAL]
15
+ INT_TYPES = [MysqlField::TYPE_TINY,MysqlField::TYPE_SHORT,MysqlField::TYPE_LONG,MysqlField::TYPE_LONGLONG,MysqlField::TYPE_INT24]
16
+
17
+ def self.guess_type(value, meta)
18
+ return nil if value.nil?
19
+
20
+ custom_type = DohDb::find_column_type(nil, meta.table, meta.name) || DohDb::find_column_type(nil, nil, meta.name)
21
+ return custom_type.build(meta.name, value) if custom_type
22
+
23
+ return DohDb::parse_bool(value) if (meta.type == MysqlField::TYPE_TINY) && (meta.length == 1) && (meta.max_length == 1)
24
+ return DohDb::parse_datetime(value) if meta.type == MysqlField::TYPE_DATETIME
25
+ return DohDb::parse_date(value) if meta.type == MysqlField::TYPE_DATE
26
+ return DohDb::parse_decimal(value) if DECIMAL_TYPES.include?(meta.type)
27
+ return DohDb::parse_int(value) if INT_TYPES.include?(meta.type)
28
+ if meta.type == MysqlField::TYPE_STRING || meta.type == MysqlField::TYPE_VAR_STRING
29
+ return PhoneDisplayString.new(value) if (value.size == 10) && (meta.name.lastn(5) == 'phone')
30
+ return SsnDisplayString.new(value) if (value.size == 9) && (meta.max_length == 9) && (meta.name.lastn(3) == 'ssn')
31
+ return PostalDisplayString.new(value) if [5,9].include?(value.size) && [5,9].include?(meta.max_length) && ((meta.name.lastn(6) == 'postal') || (meta.name.lastn(3) == 'zip'))
32
+ end
33
+ value
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,7 @@
1
+ module DohDb
2
+
3
+ class UnexpectedQueryResult < StandardError; end
4
+ class UnknownColumn < StandardError; end
5
+ class CannotBeNull < StandardError; end
6
+
7
+ end