tina4 0.3.0 → 0.5.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tina4/version.rb +1 -1
  3. data/lib/tina4ruby.rb +4 -0
  4. metadata +14 -258
  5. data/CHANGELOG.md +0 -73
  6. data/LICENSE.txt +0 -21
  7. data/README.md +0 -662
  8. data/exe/tina4 +0 -4
  9. data/lib/tina4/api.rb +0 -152
  10. data/lib/tina4/auth.rb +0 -139
  11. data/lib/tina4/cli.rb +0 -243
  12. data/lib/tina4/crud.rb +0 -124
  13. data/lib/tina4/database.rb +0 -135
  14. data/lib/tina4/database_result.rb +0 -89
  15. data/lib/tina4/debug.rb +0 -83
  16. data/lib/tina4/dev_reload.rb +0 -68
  17. data/lib/tina4/drivers/firebird_driver.rb +0 -94
  18. data/lib/tina4/drivers/mssql_driver.rb +0 -112
  19. data/lib/tina4/drivers/mysql_driver.rb +0 -90
  20. data/lib/tina4/drivers/postgres_driver.rb +0 -99
  21. data/lib/tina4/drivers/sqlite_driver.rb +0 -85
  22. data/lib/tina4/env.rb +0 -55
  23. data/lib/tina4/field_types.rb +0 -84
  24. data/lib/tina4/graphql.rb +0 -837
  25. data/lib/tina4/localization.rb +0 -100
  26. data/lib/tina4/middleware.rb +0 -59
  27. data/lib/tina4/migration.rb +0 -124
  28. data/lib/tina4/orm.rb +0 -168
  29. data/lib/tina4/queue.rb +0 -117
  30. data/lib/tina4/queue_backends/kafka_backend.rb +0 -80
  31. data/lib/tina4/queue_backends/lite_backend.rb +0 -79
  32. data/lib/tina4/queue_backends/rabbitmq_backend.rb +0 -73
  33. data/lib/tina4/rack_app.rb +0 -150
  34. data/lib/tina4/request.rb +0 -158
  35. data/lib/tina4/response.rb +0 -172
  36. data/lib/tina4/router.rb +0 -142
  37. data/lib/tina4/scss_compiler.rb +0 -131
  38. data/lib/tina4/session.rb +0 -145
  39. data/lib/tina4/session_handlers/file_handler.rb +0 -55
  40. data/lib/tina4/session_handlers/mongo_handler.rb +0 -49
  41. data/lib/tina4/session_handlers/redis_handler.rb +0 -43
  42. data/lib/tina4/swagger.rb +0 -123
  43. data/lib/tina4/template.rb +0 -478
  44. data/lib/tina4/templates/base.twig +0 -25
  45. data/lib/tina4/templates/errors/403.twig +0 -22
  46. data/lib/tina4/templates/errors/404.twig +0 -22
  47. data/lib/tina4/templates/errors/500.twig +0 -22
  48. data/lib/tina4/testing.rb +0 -213
  49. data/lib/tina4/webserver.rb +0 -101
  50. data/lib/tina4/websocket.rb +0 -167
  51. data/lib/tina4/wsdl.rb +0 -164
  52. data/lib/tina4.rb +0 -234
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
- require "json"
3
-
4
- module Tina4
5
- class Database
6
- attr_reader :driver, :driver_name, :connected
7
-
8
- DRIVERS = {
9
- "sqlite" => "Tina4::Drivers::SqliteDriver",
10
- "sqlite3" => "Tina4::Drivers::SqliteDriver",
11
- "postgres" => "Tina4::Drivers::PostgresDriver",
12
- "postgresql" => "Tina4::Drivers::PostgresDriver",
13
- "mysql" => "Tina4::Drivers::MysqlDriver",
14
- "mysql2" => "Tina4::Drivers::MysqlDriver",
15
- "mssql" => "Tina4::Drivers::MssqlDriver",
16
- "sqlserver" => "Tina4::Drivers::MssqlDriver",
17
- "firebird" => "Tina4::Drivers::FirebirdDriver"
18
- }.freeze
19
-
20
- def initialize(connection_string, driver_name: nil)
21
- @connection_string = connection_string
22
- @driver_name = driver_name || detect_driver(connection_string)
23
- @driver = create_driver
24
- @connected = false
25
- connect
26
- end
27
-
28
- def connect
29
- @driver.connect(@connection_string)
30
- @connected = true
31
- Tina4::Debug.info("Database connected: #{@driver_name}")
32
- rescue => e
33
- Tina4::Debug.error("Database connection failed: #{e.message}")
34
- @connected = false
35
- end
36
-
37
- def close
38
- @driver.close if @connected
39
- @connected = false
40
- end
41
-
42
- def fetch(sql, params = [], limit: nil, skip: nil)
43
- effective_sql = sql
44
- if limit
45
- effective_sql = @driver.apply_limit(effective_sql, limit, skip || 0)
46
- end
47
- rows = @driver.execute_query(effective_sql, params)
48
- Tina4::DatabaseResult.new(rows, sql: effective_sql)
49
- end
50
-
51
- def fetch_one(sql, params = [])
52
- result = fetch(sql, params, limit: 1)
53
- result.first
54
- end
55
-
56
- def insert(table, data)
57
- columns = data.keys.map(&:to_s)
58
- placeholders = @driver.placeholders(columns.length)
59
- sql = "INSERT INTO #{table} (#{columns.join(', ')}) VALUES (#{placeholders})"
60
- @driver.execute(sql, data.values)
61
- { success: true, last_id: @driver.last_insert_id }
62
- end
63
-
64
- def update(table, data, filter = {})
65
- set_parts = data.keys.map { |k| "#{k} = #{@driver.placeholder}" }
66
- where_parts = filter.keys.map { |k| "#{k} = #{@driver.placeholder}" }
67
- sql = "UPDATE #{table} SET #{set_parts.join(', ')}"
68
- sql += " WHERE #{where_parts.join(' AND ')}" unless filter.empty?
69
- values = data.values + filter.values
70
- @driver.execute(sql, values)
71
- { success: true }
72
- end
73
-
74
- def delete(table, filter = {})
75
- where_parts = filter.keys.map { |k| "#{k} = #{@driver.placeholder}" }
76
- sql = "DELETE FROM #{table}"
77
- sql += " WHERE #{where_parts.join(' AND ')}" unless filter.empty?
78
- @driver.execute(sql, filter.values)
79
- { success: true }
80
- end
81
-
82
- def execute(sql, params = [])
83
- @driver.execute(sql, params)
84
- end
85
-
86
- def transaction
87
- @driver.begin_transaction
88
- yield self
89
- @driver.commit
90
- rescue => e
91
- @driver.rollback
92
- raise e
93
- end
94
-
95
- def tables
96
- @driver.tables
97
- end
98
-
99
- def columns(table_name)
100
- @driver.columns(table_name)
101
- end
102
-
103
- def table_exists?(table_name)
104
- tables.any? { |t| t.downcase == table_name.to_s.downcase }
105
- end
106
-
107
- private
108
-
109
- def detect_driver(conn)
110
- case conn.to_s.downcase
111
- when /\.db$/, /\.sqlite/, /sqlite/
112
- "sqlite"
113
- when /postgres/, /^pg:/
114
- "postgres"
115
- when /mysql/
116
- "mysql"
117
- when /mssql/, /sqlserver/
118
- "mssql"
119
- when /firebird/, /\.fdb$/
120
- "firebird"
121
- else
122
- "sqlite"
123
- end
124
- end
125
-
126
- def create_driver
127
- klass_name = DRIVERS[@driver_name]
128
- raise "Unknown database driver: #{@driver_name}" unless klass_name
129
- klass = Object.const_get(klass_name)
130
- klass.new
131
- rescue NameError
132
- raise "Driver #{klass_name} not loaded. Install the required gem."
133
- end
134
- end
135
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
- require "json"
3
-
4
- module Tina4
5
- class DatabaseResult
6
- include Enumerable
7
-
8
- attr_reader :records, :sql, :count
9
-
10
- def initialize(records = [], sql: "")
11
- @records = records || []
12
- @sql = sql
13
- @count = @records.length
14
- end
15
-
16
- def each(&block)
17
- @records.each(&block)
18
- end
19
-
20
- def first
21
- @records.first
22
- end
23
-
24
- def last
25
- @records.last
26
- end
27
-
28
- def empty?
29
- @records.empty?
30
- end
31
-
32
- def [](index)
33
- @records[index]
34
- end
35
-
36
- def to_array
37
- @records.map do |record|
38
- record.is_a?(Hash) ? record : record.to_h
39
- end
40
- end
41
-
42
- def to_json(*_args)
43
- JSON.generate(to_array)
44
- end
45
-
46
- def to_csv(separator: ",", headers: true)
47
- return "" if @records.empty?
48
- lines = []
49
- cols = @records.first.keys
50
- lines << cols.join(separator) if headers
51
- @records.each do |row|
52
- lines << cols.map { |c| escape_csv(row[c], separator) }.join(separator)
53
- end
54
- lines.join("\n")
55
- end
56
-
57
- def to_paginate(page: 1, per_page: 10)
58
- total = @records.length
59
- total_pages = (total.to_f / per_page).ceil
60
- offset = (page - 1) * per_page
61
- page_records = @records[offset, per_page] || []
62
- {
63
- data: page_records,
64
- page: page,
65
- per_page: per_page,
66
- total: total,
67
- total_pages: total_pages,
68
- has_next: page < total_pages,
69
- has_prev: page > 1
70
- }
71
- end
72
-
73
- def to_crud(table_name: "data", primary_key: "id", editable: true)
74
- Tina4::Crud.generate_table(@records, table_name: table_name,
75
- primary_key: primary_key, editable: editable)
76
- end
77
-
78
- private
79
-
80
- def escape_csv(value, separator)
81
- str = value.to_s
82
- if str.include?(separator) || str.include?('"') || str.include?("\n")
83
- "\"#{str.gsub('"', '""')}\""
84
- else
85
- str
86
- end
87
- end
88
- end
89
- end
data/lib/tina4/debug.rb DELETED
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
- require "logger"
3
- require "fileutils"
4
-
5
- module Tina4
6
- module Debug
7
- LEVELS = {
8
- "[TINA4_LOG_ALL]" => Logger::DEBUG,
9
- "[TINA4_LOG_DEBUG]" => Logger::DEBUG,
10
- "[TINA4_LOG_INFO]" => Logger::INFO,
11
- "[TINA4_LOG_WARNING]" => Logger::WARN,
12
- "[TINA4_LOG_ERROR]" => Logger::ERROR,
13
- "[TINA4_LOG_NONE]" => Logger::FATAL
14
- }.freeze
15
-
16
- COLORS = {
17
- reset: "\e[0m", red: "\e[31m", green: "\e[32m",
18
- yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m",
19
- cyan: "\e[36m", gray: "\e[90m"
20
- }.freeze
21
-
22
- class << self
23
- def setup(root_dir = Dir.pwd)
24
- log_dir = File.join(root_dir, "logs")
25
- FileUtils.mkdir_p(log_dir)
26
- log_file = File.join(log_dir, "debug.log")
27
- @file_logger = Logger.new(log_file, 10, 5 * 1024 * 1024)
28
- @file_logger.level = Logger::DEBUG
29
- @console_logger = Logger.new($stdout)
30
- @console_logger.level = resolve_level
31
- @console_logger.formatter = method(:color_formatter)
32
- @file_logger.formatter = method(:plain_formatter)
33
- @initialized = true
34
- end
35
-
36
- def info(message, *args)
37
- log(:info, message, *args)
38
- end
39
-
40
- def debug(message, *args)
41
- log(:debug, message, *args)
42
- end
43
-
44
- def warning(message, *args)
45
- log(:warn, message, *args)
46
- end
47
-
48
- def error(message, *args)
49
- log(:error, message, *args)
50
- end
51
-
52
- private
53
-
54
- def log(level, message, *args)
55
- setup unless @initialized
56
- full_message = args.empty? ? message.to_s : "#{message} #{args.map(&:to_s).join(' ')}"
57
- @console_logger.send(level, full_message)
58
- @file_logger.send(level, full_message)
59
- end
60
-
61
- def resolve_level
62
- env_level = ENV["TINA4_DEBUG_LEVEL"] || "[TINA4_LOG_ALL]"
63
- LEVELS[env_level] || Logger::DEBUG
64
- end
65
-
66
- def color_formatter(severity, datetime, _progname, message)
67
- color = case severity
68
- when "DEBUG" then COLORS[:gray]
69
- when "INFO" then COLORS[:green]
70
- when "WARN" then COLORS[:yellow]
71
- when "ERROR" then COLORS[:red]
72
- else COLORS[:reset]
73
- end
74
- ts = datetime.strftime("%Y-%m-%d %H:%M:%S")
75
- "#{COLORS[:gray]}[#{ts}]#{COLORS[:reset]} #{color}[#{severity}]#{COLORS[:reset]} #{message}\n"
76
- end
77
-
78
- def plain_formatter(severity, datetime, _progname, message)
79
- "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] [#{severity}] #{message}\n"
80
- end
81
- end
82
- end
83
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tina4
4
- module DevReload
5
- WATCH_EXTENSIONS = %w[.rb .twig .html .erb .scss .css .js].freeze
6
- WATCH_DIRS = %w[src routes lib templates public].freeze
7
- IGNORE_DIRS = %w[.git node_modules vendor logs sessions .queue .keys].freeze
8
-
9
- class << self
10
- def start(root_dir: Dir.pwd, &on_change)
11
- require "listen"
12
-
13
- dirs = WATCH_DIRS
14
- .map { |d| File.join(root_dir, d) }
15
- .select { |d| Dir.exist?(d) }
16
-
17
- # Also watch root for .rb files
18
- dirs << root_dir
19
-
20
- Tina4::Debug.info("Dev reload watching: #{dirs.join(', ')}")
21
-
22
- @listener = Listen.to(*dirs, only: /\.(#{WATCH_EXTENSIONS.map { |e| e.delete('.') }.join('|')})$/, ignore: build_ignore_regex) do |modified, added, removed|
23
- changes = { modified: modified, added: added, removed: removed }
24
- all_files = modified + added + removed
25
- next if all_files.empty?
26
-
27
- Tina4::Debug.info("File changes detected:")
28
- modified.each { |f| Tina4::Debug.debug(" Modified: #{f}") }
29
- added.each { |f| Tina4::Debug.debug(" Added: #{f}") }
30
- removed.each { |f| Tina4::Debug.debug(" Removed: #{f}") }
31
-
32
- # Reload Ruby files
33
- modified.select { |f| f.end_with?(".rb") }.each do |file|
34
- begin
35
- load file
36
- Tina4::Debug.info("Reloaded: #{file}")
37
- rescue => e
38
- Tina4::Debug.error("Reload failed: #{file} - #{e.message}")
39
- end
40
- end
41
-
42
- # Recompile SCSS
43
- scss_changes = all_files.select { |f| f.end_with?(".scss") }
44
- if scss_changes.any?
45
- Tina4::ScssCompiler.compile_all(root_dir)
46
- end
47
-
48
- on_change&.call(changes)
49
- end
50
-
51
- @listener.start
52
- Tina4::Debug.info("Dev reload started")
53
- end
54
-
55
- def stop
56
- @listener&.stop
57
- Tina4::Debug.info("Dev reload stopped")
58
- end
59
-
60
- private
61
-
62
- def build_ignore_regex
63
- pattern = IGNORE_DIRS.map { |d| Regexp.escape(d) }.join("|")
64
- /#{pattern}/
65
- end
66
- end
67
- end
68
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tina4
4
- module Drivers
5
- class FirebirdDriver
6
- attr_reader :connection
7
-
8
- def connect(connection_string)
9
- require "fb"
10
- db_path = connection_string.sub(/^firebird:\/\//, "")
11
- @connection = Fb::Database.new(database: db_path).connect
12
- rescue LoadError
13
- raise "Firebird driver requires the 'fb' gem. Install it with: gem install fb"
14
- end
15
-
16
- def close
17
- @connection&.close
18
- end
19
-
20
- def execute_query(sql, params = [])
21
- if params.empty?
22
- @connection.query(:hash, sql)
23
- else
24
- @connection.query(:hash, sql, *params)
25
- end.map { |row| stringify_keys(row) }
26
- end
27
-
28
- def execute(sql, params = [])
29
- if params.empty?
30
- @connection.execute(sql)
31
- else
32
- @connection.execute(sql, *params)
33
- end
34
- end
35
-
36
- def last_insert_id
37
- nil
38
- end
39
-
40
- def placeholder
41
- "?"
42
- end
43
-
44
- def placeholders(count)
45
- (["?"] * count).join(", ")
46
- end
47
-
48
- def apply_limit(sql, limit, offset = 0)
49
- "SELECT FIRST #{limit} SKIP #{offset} * FROM (#{sql})"
50
- end
51
-
52
- def begin_transaction
53
- @transaction = @connection.transaction
54
- end
55
-
56
- def commit
57
- @transaction&.commit
58
- end
59
-
60
- def rollback
61
- @transaction&.rollback
62
- end
63
-
64
- def tables
65
- sql = "SELECT RDB\$RELATION_NAME FROM RDB\$RELATIONS WHERE RDB\$SYSTEM_FLAG = 0 AND RDB\$VIEW_BLR IS NULL"
66
- rows = execute_query(sql)
67
- rows.map { |r| (r["RDB\$RELATION_NAME"] || r["rdb\$relation_name"] || "").strip }
68
- end
69
-
70
- def columns(table_name)
71
- sql = "SELECT RF.RDB\$FIELD_NAME, F.RDB\$FIELD_TYPE, RF.RDB\$NULL_FLAG, RF.RDB\$DEFAULT_SOURCE " \
72
- "FROM RDB\$RELATION_FIELDS RF " \
73
- "JOIN RDB\$FIELDS F ON RF.RDB\$FIELD_SOURCE = F.RDB\$FIELD_NAME " \
74
- "WHERE RF.RDB\$RELATION_NAME = ?"
75
- rows = execute_query(sql, [table_name.upcase])
76
- rows.map do |r|
77
- {
78
- name: (r["RDB\$FIELD_NAME"] || r["rdb\$field_name"] || "").strip,
79
- type: r["RDB\$FIELD_TYPE"] || r["rdb\$field_type"],
80
- nullable: (r["RDB\$NULL_FLAG"] || r["rdb\$null_flag"]).nil?,
81
- default: r["RDB\$DEFAULT_SOURCE"] || r["rdb\$default_source"],
82
- primary_key: false
83
- }
84
- end
85
- end
86
-
87
- private
88
-
89
- def stringify_keys(hash)
90
- hash.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
91
- end
92
- end
93
- end
94
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tina4
4
- module Drivers
5
- class MssqlDriver
6
- attr_reader :connection
7
-
8
- def connect(connection_string)
9
- require "tiny_tds"
10
- uri = parse_connection(connection_string)
11
- @connection = TinyTds::Client.new(
12
- host: uri[:host],
13
- port: uri[:port] || 1433,
14
- username: uri[:username],
15
- password: uri[:password],
16
- database: uri[:database]
17
- )
18
- end
19
-
20
- def close
21
- @connection&.close
22
- end
23
-
24
- def execute_query(sql, params = [])
25
- effective_sql = interpolate_params(sql, params)
26
- result = @connection.execute(effective_sql)
27
- rows = result.each(symbolize_keys: true).to_a
28
- result.cancel if result.respond_to?(:cancel)
29
- rows
30
- end
31
-
32
- def execute(sql, params = [])
33
- effective_sql = interpolate_params(sql, params)
34
- result = @connection.execute(effective_sql)
35
- result.do
36
- end
37
-
38
- def last_insert_id
39
- result = @connection.execute("SELECT SCOPE_IDENTITY() AS id")
40
- row = result.first
41
- result.cancel if result.respond_to?(:cancel)
42
- row[:id]&.to_i
43
- end
44
-
45
- def placeholder
46
- "?"
47
- end
48
-
49
- def placeholders(count)
50
- (["?"] * count).join(", ")
51
- end
52
-
53
- def apply_limit(sql, limit, offset = 0)
54
- "#{sql} OFFSET #{offset} ROWS FETCH NEXT #{limit} ROWS ONLY"
55
- end
56
-
57
- def begin_transaction
58
- @connection.execute("BEGIN TRANSACTION").do
59
- end
60
-
61
- def commit
62
- @connection.execute("COMMIT").do
63
- end
64
-
65
- def rollback
66
- @connection.execute("ROLLBACK").do
67
- end
68
-
69
- def tables
70
- rows = execute_query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
71
- rows.map { |r| r[:TABLE_NAME] || r[:table_name] }
72
- end
73
-
74
- def columns(table_name)
75
- sql = "SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?"
76
- rows = execute_query(sql, [table_name])
77
- rows.map do |r|
78
- {
79
- name: r[:COLUMN_NAME] || r[:column_name],
80
- type: r[:DATA_TYPE] || r[:data_type],
81
- nullable: (r[:IS_NULLABLE] || r[:is_nullable]) == "YES",
82
- default: r[:COLUMN_DEFAULT] || r[:column_default],
83
- primary_key: false
84
- }
85
- end
86
- end
87
-
88
- private
89
-
90
- def parse_connection(str)
91
- # Format: mssql://user:pass@host:port/database
92
- match = str.match(%r{(?:mssql|sqlserver)://(?:(\w+):([^@]+)@)?([^:/]+)(?::(\d+))?/(.+)})
93
- if match
94
- { username: match[1], password: match[2], host: match[3],
95
- port: match[4]&.to_i, database: match[5] }
96
- else
97
- { host: "localhost", database: str }
98
- end
99
- end
100
-
101
- def interpolate_params(sql, params)
102
- return sql if params.empty?
103
- result = sql.dup
104
- params.each do |param|
105
- escaped = param.is_a?(String) ? "'#{param.gsub("'", "''")}'" : param.to_s
106
- result = result.sub("?", escaped)
107
- end
108
- result
109
- end
110
- end
111
- end
112
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tina4
4
- module Drivers
5
- class MysqlDriver
6
- attr_reader :connection
7
-
8
- def connect(connection_string)
9
- require "mysql2"
10
- uri = URI.parse(connection_string.sub(/^mysql:\/\//, "mysql2://"))
11
- @connection = Mysql2::Client.new(
12
- host: uri.host || "localhost",
13
- port: uri.port || 3306,
14
- username: uri.user,
15
- password: uri.password,
16
- database: uri.path&.sub("/", "")
17
- )
18
- end
19
-
20
- def close
21
- @connection&.close
22
- end
23
-
24
- def execute_query(sql, params = [])
25
- if params.empty?
26
- results = @connection.query(sql, symbolize_keys: true)
27
- else
28
- stmt = @connection.prepare(sql)
29
- results = stmt.execute(*params, symbolize_keys: true)
30
- end
31
- results.to_a
32
- end
33
-
34
- def execute(sql, params = [])
35
- if params.empty?
36
- @connection.query(sql)
37
- else
38
- stmt = @connection.prepare(sql)
39
- stmt.execute(*params)
40
- end
41
- end
42
-
43
- def last_insert_id
44
- @connection.last_id
45
- end
46
-
47
- def placeholder
48
- "?"
49
- end
50
-
51
- def placeholders(count)
52
- (["?"] * count).join(", ")
53
- end
54
-
55
- def apply_limit(sql, limit, offset = 0)
56
- "#{sql} LIMIT #{limit} OFFSET #{offset}"
57
- end
58
-
59
- def begin_transaction
60
- @connection.query("START TRANSACTION")
61
- end
62
-
63
- def commit
64
- @connection.query("COMMIT")
65
- end
66
-
67
- def rollback
68
- @connection.query("ROLLBACK")
69
- end
70
-
71
- def tables
72
- rows = execute_query("SHOW TABLES")
73
- rows.map { |r| r.values.first }
74
- end
75
-
76
- def columns(table_name)
77
- rows = execute_query("DESCRIBE #{table_name}")
78
- rows.map do |r|
79
- {
80
- name: r[:Field],
81
- type: r[:Type],
82
- nullable: r[:Null] == "YES",
83
- default: r[:Default],
84
- primary_key: r[:Key] == "PRI"
85
- }
86
- end
87
- end
88
- end
89
- end
90
- end