dohmysql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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