structured_data_to_sql 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.
- checksums.yaml +7 -0
- data/LICENSE +9 -0
- data/bin/structured-data-to-sql +6 -0
- data/lib/structured_data_to_sql/cli.rb +405 -0
- data/lib/structured_data_to_sql/conversion_result.rb +94 -0
- data/lib/structured_data_to_sql/diagnostic.rb +82 -0
- data/lib/structured_data_to_sql/diagnostics_report.rb +187 -0
- data/lib/structured_data_to_sql/errors.rb +48 -0
- data/lib/structured_data_to_sql/format.rb +41 -0
- data/lib/structured_data_to_sql/io_support.rb +154 -0
- data/lib/structured_data_to_sql/json/exporter_manifest.rb +127 -0
- data/lib/structured_data_to_sql/json/json_schema_loader.rb +310 -0
- data/lib/structured_data_to_sql/json/profiles/khoros_api_export.rb +1960 -0
- data/lib/structured_data_to_sql/json/profiles.rb +14 -0
- data/lib/structured_data_to_sql/json/record_streamer.rb +477 -0
- data/lib/structured_data_to_sql/json/schema_inferrer.rb +150 -0
- data/lib/structured_data_to_sql/json/shredder.rb +241 -0
- data/lib/structured_data_to_sql/json/sql_emitter.rb +198 -0
- data/lib/structured_data_to_sql/json_converter.rb +913 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/invalid_character_report.rb +82 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/sanitizer.rb +152 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/sax_parser.rb +111 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/sql_emitter.rb +104 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/table_data_filter.rb +343 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/table_discovery.rb +651 -0
- data/lib/structured_data_to_sql/mysql_dump_xml/table_structure.rb +98 -0
- data/lib/structured_data_to_sql/mysql_dump_xml_converter.rb +4 -0
- data/lib/structured_data_to_sql/options.rb +89 -0
- data/lib/structured_data_to_sql/progress_reporter.rb +348 -0
- data/lib/structured_data_to_sql/sql_text.rb +29 -0
- data/lib/structured_data_to_sql/version.rb +5 -0
- data/lib/structured_data_to_sql/xml_converter.rb +651 -0
- data/lib/structured_data_to_sql/xml_dump_converter.rb +4 -0
- data/lib/structured_data_to_sql.rb +54 -0
- metadata +120 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "oj"
|
|
5
|
+
|
|
6
|
+
module StructuredDataToSql
|
|
7
|
+
module Json
|
|
8
|
+
# A JSON value kept verbatim as serialized JSON text (fallback for shapes
|
|
9
|
+
# that do not shred cleanly into columns or child tables).
|
|
10
|
+
JsonValue = Struct.new(:text)
|
|
11
|
+
|
|
12
|
+
# One shredded record: +table_rel+ is the table's path relative to the
|
|
13
|
+
# root table ("" for the root itself), +columns+ maps final column names
|
|
14
|
+
# to raw values, +children+ holds nested ShreddedRows for child tables.
|
|
15
|
+
ShreddedRow =
|
|
16
|
+
Struct.new(:table_rel, :columns, :children, :ordinal, :parent_natural_id)
|
|
17
|
+
|
|
18
|
+
# Allocates deterministic, collision-free table and column names. Built
|
|
19
|
+
# during the inference pass and reused verbatim during the emit pass so
|
|
20
|
+
# both passes agree on every name.
|
|
21
|
+
class NameRegistry
|
|
22
|
+
MAX_NAME_LENGTH = 64
|
|
23
|
+
|
|
24
|
+
attr_reader :collisions
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@columns = {}
|
|
28
|
+
@column_owner = Hash.new { |hash, key| hash[key] = {} }
|
|
29
|
+
@children = {}
|
|
30
|
+
@child_owner = Hash.new { |hash, key| hash[key] = {} }
|
|
31
|
+
@collisions = 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def column_name(table_rel, raw_path, representation)
|
|
35
|
+
key = [table_rel, representation, raw_path.join("\0")]
|
|
36
|
+
@columns[key] ||= allocate(@column_owner[table_rel], key) do
|
|
37
|
+
base = sanitize_path(raw_path)
|
|
38
|
+
representation == :json ? "#{base}_json" : base
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def child_rel(table_rel, raw_path)
|
|
43
|
+
key = [table_rel, raw_path.join("\0")]
|
|
44
|
+
@children[key] ||= allocate(@child_owner[:tables], key) do
|
|
45
|
+
rel = sanitize_path(raw_path)
|
|
46
|
+
table_rel.empty? ? rel : "#{table_rel}_#{rel}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.sanitize(name)
|
|
51
|
+
sanitized =
|
|
52
|
+
name
|
|
53
|
+
.to_s
|
|
54
|
+
.gsub(/([a-z0-9])([A-Z])/, '\1_\2')
|
|
55
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
56
|
+
.downcase
|
|
57
|
+
.gsub(/[^a-z0-9_]+/, "_")
|
|
58
|
+
.squeeze("_")
|
|
59
|
+
.gsub(/\A_+|_+\z/, "")
|
|
60
|
+
sanitized = "t_#{sanitized}" if sanitized.match?(/\A\d/)
|
|
61
|
+
sanitized.empty? ? "unnamed" : sanitized
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.truncate(name)
|
|
65
|
+
return name if name.length <= MAX_NAME_LENGTH
|
|
66
|
+
|
|
67
|
+
"#{name[0, MAX_NAME_LENGTH - 9]}_#{Digest::SHA1.hexdigest(name)[0, 8]}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def sanitize_path(raw_path)
|
|
73
|
+
self.class.truncate(
|
|
74
|
+
raw_path.map { |part| self.class.sanitize(part) }.join("_")
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def allocate(taken, key)
|
|
79
|
+
candidate = self.class.truncate(yield)
|
|
80
|
+
unless taken[candidate].nil? || taken[candidate] == key
|
|
81
|
+
@collisions += 1
|
|
82
|
+
suffix = 2
|
|
83
|
+
suffix += 1 until taken[
|
|
84
|
+
self.class.truncate("#{candidate}_#{suffix}")
|
|
85
|
+
].nil?
|
|
86
|
+
candidate = self.class.truncate("#{candidate}_#{suffix}")
|
|
87
|
+
end
|
|
88
|
+
taken[candidate] = key
|
|
89
|
+
candidate
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Turns one JSON record into a tree of ShreddedRows following the
|
|
94
|
+
# convention-based rules: nested objects flatten into prefixed columns,
|
|
95
|
+
# arrays become child tables, GraphQL edges/node wrappers unwrap, and
|
|
96
|
+
# anything too deep or irregular falls back to a *_json text column.
|
|
97
|
+
class Shredder
|
|
98
|
+
def initialize(
|
|
99
|
+
registry:,
|
|
100
|
+
max_depth: 5,
|
|
101
|
+
graphql_unwrap: true,
|
|
102
|
+
json_column_paths: nil,
|
|
103
|
+
forced_json_paths: nil
|
|
104
|
+
)
|
|
105
|
+
@registry = registry
|
|
106
|
+
@max_depth = max_depth
|
|
107
|
+
@graphql_unwrap = graphql_unwrap
|
|
108
|
+
@json_column_paths = (json_column_paths || []).to_set
|
|
109
|
+
@forced_json_paths = forced_json_paths || Set.new
|
|
110
|
+
@json_fallbacks = 0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
attr_reader :json_fallbacks
|
|
114
|
+
|
|
115
|
+
def shred(record, ordinal)
|
|
116
|
+
build_row("", record, ordinal, nil)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def build_row(table_rel, record, ordinal, parent_natural_id)
|
|
122
|
+
row = ShreddedRow.new(table_rel, {}, [], ordinal, parent_natural_id)
|
|
123
|
+
case record
|
|
124
|
+
when Hash
|
|
125
|
+
walk(record, [], row, record)
|
|
126
|
+
when Array
|
|
127
|
+
add_json_column(row, ["value"], record)
|
|
128
|
+
else
|
|
129
|
+
row.columns["value"] = record
|
|
130
|
+
end
|
|
131
|
+
row
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def walk(object, path, row, natural_id_source)
|
|
135
|
+
object.each do |key, value|
|
|
136
|
+
new_path = path + [key]
|
|
137
|
+
if forced_json?(row.table_rel, new_path)
|
|
138
|
+
add_json_column(row, new_path, value)
|
|
139
|
+
next
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
case value
|
|
143
|
+
when Hash
|
|
144
|
+
if @graphql_unwrap && (nodes = unwrap_edges(value))
|
|
145
|
+
handle_array(nodes, new_path, row, natural_id_source)
|
|
146
|
+
elsif new_path.length >= @max_depth
|
|
147
|
+
add_json_column(row, new_path, value)
|
|
148
|
+
else
|
|
149
|
+
walk(value, new_path, row, natural_id_source)
|
|
150
|
+
end
|
|
151
|
+
when Array
|
|
152
|
+
handle_array(value, new_path, row, natural_id_source)
|
|
153
|
+
else
|
|
154
|
+
row.columns[
|
|
155
|
+
@registry.column_name(row.table_rel, new_path, :scalar)
|
|
156
|
+
] = value
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def handle_array(array, path, row, natural_id_source)
|
|
162
|
+
return if array.empty?
|
|
163
|
+
return add_json_column(row, path, array) if path.length >= @max_depth
|
|
164
|
+
|
|
165
|
+
hashes, others = array.partition { |element| element.is_a?(Hash) }
|
|
166
|
+
if hashes.any? && others.any? ||
|
|
167
|
+
array.any? { |element| element.is_a?(Array) }
|
|
168
|
+
add_json_column(row, path, array)
|
|
169
|
+
elsif hashes.any?
|
|
170
|
+
rel = @registry.child_rel(row.table_rel, path)
|
|
171
|
+
natural_id = natural_id_of(natural_id_source)
|
|
172
|
+
array.each_with_index do |element, index|
|
|
173
|
+
row.children << build_row(rel, element, index, natural_id)
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
rel = @registry.child_rel(row.table_rel, path)
|
|
177
|
+
natural_id = natural_id_of(natural_id_source)
|
|
178
|
+
array.each_with_index do |element, index|
|
|
179
|
+
row.children << ShreddedRow.new(
|
|
180
|
+
rel,
|
|
181
|
+
{ "value" => element },
|
|
182
|
+
[],
|
|
183
|
+
index,
|
|
184
|
+
natural_id
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def add_json_column(row, path, value)
|
|
191
|
+
@json_fallbacks += 1
|
|
192
|
+
row.columns[
|
|
193
|
+
@registry.column_name(row.table_rel, path, :json)
|
|
194
|
+
] = JsonValue.new(Oj.dump(value, mode: :compat))
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def forced_json?(table_rel, path)
|
|
198
|
+
return true if @forced_json_paths.include?([table_rel, path.join("\0")])
|
|
199
|
+
return false if @json_column_paths.empty? || !table_rel.empty?
|
|
200
|
+
|
|
201
|
+
@json_column_paths.include?(
|
|
202
|
+
path.map { |part| NameRegistry.sanitize(part) }.join(".")
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Unwraps a Relay-style connection object to the array of node values.
|
|
207
|
+
#
|
|
208
|
+
# Tolerates the standard Relay connection shape where sibling keys like
|
|
209
|
+
# +pageInfo+ and +totalCount+ live alongside +edges+, and where each edge
|
|
210
|
+
# may carry a +cursor+ (or any other per-edge keys) in addition to +node+.
|
|
211
|
+
# Only the +node+ value is extracted from each edge; all other edge keys
|
|
212
|
+
# are silently discarded.
|
|
213
|
+
#
|
|
214
|
+
# Returns nil if the object does not look like a connection (no +edges+
|
|
215
|
+
# key, non-array edges, or any edge that is missing a +node+ key).
|
|
216
|
+
def unwrap_edges(value)
|
|
217
|
+
return nil unless value.key?("edges") && value["edges"].is_a?(Array)
|
|
218
|
+
unless value["edges"].all? { |edge|
|
|
219
|
+
edge.is_a?(Hash) && edge.key?("node")
|
|
220
|
+
}
|
|
221
|
+
return nil
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
value["edges"].map { |edge| edge["node"] }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def natural_id_of(record)
|
|
228
|
+
id = record["id"]
|
|
229
|
+
return id.to_s if id.is_a?(String) || id.is_a?(Integer)
|
|
230
|
+
|
|
231
|
+
record.each do |key, value|
|
|
232
|
+
next unless value.is_a?(String) || value.is_a?(Integer)
|
|
233
|
+
|
|
234
|
+
sanitized = NameRegistry.sanitize(key)
|
|
235
|
+
return value.to_s if sanitized == "id" || sanitized.end_with?("_id")
|
|
236
|
+
end
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require_relative "../sql_text"
|
|
5
|
+
|
|
6
|
+
module StructuredDataToSql
|
|
7
|
+
module Json
|
|
8
|
+
# Writes MySQL/MariaDB DDL and batched INSERT statements following the
|
|
9
|
+
# same conventions as the XML converter output.
|
|
10
|
+
class SqlEmitter
|
|
11
|
+
META_ROOT = ["_sid"].freeze
|
|
12
|
+
META_CHILD = %w[_sid _parent_sid _parent_id _ordinal].freeze
|
|
13
|
+
MAX_INLINE_ROW_BYTES = 60_000
|
|
14
|
+
TEXT_POINTER_BYTES = 20
|
|
15
|
+
|
|
16
|
+
def write_header(out, source)
|
|
17
|
+
out.write("-- Generated by json-to-sql converter\n")
|
|
18
|
+
out.write("-- Source: #{source}\n")
|
|
19
|
+
out.write(
|
|
20
|
+
"-- Generated at: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}\n\n"
|
|
21
|
+
)
|
|
22
|
+
out.write(
|
|
23
|
+
"SET NAMES utf8mb4;\nSET sql_mode = '';\nSET FOREIGN_KEY_CHECKS = 0;\nSET UNIQUE_CHECKS = 0;\n\n"
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write_footer(out)
|
|
28
|
+
out.write("\nSET FOREIGN_KEY_CHECKS = 1;\nSET UNIQUE_CHECKS = 1;\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write_create_table(out, name, column_defs, child:)
|
|
32
|
+
column_defs = row_safe_column_defs(column_defs, child:)
|
|
33
|
+
lines = [" `_sid` BIGINT NOT NULL"]
|
|
34
|
+
if child
|
|
35
|
+
lines << " `_parent_sid` BIGINT NOT NULL"
|
|
36
|
+
lines << " `_parent_id` VARCHAR(255)"
|
|
37
|
+
lines << " `_ordinal` BIGINT NOT NULL"
|
|
38
|
+
end
|
|
39
|
+
column_defs.each do |col|
|
|
40
|
+
definition =
|
|
41
|
+
" #{SqlText.escape_identifier(col.name)} #{col.sql_type}"
|
|
42
|
+
definition += " NOT NULL" unless col.null
|
|
43
|
+
definition +=
|
|
44
|
+
" COMMENT #{SqlText.escape_sql_string(col.comment)}" if col.comment
|
|
45
|
+
lines << definition
|
|
46
|
+
end
|
|
47
|
+
lines << " PRIMARY KEY (`_sid`)"
|
|
48
|
+
if child
|
|
49
|
+
lines << " KEY `idx_parent_sid` (`_parent_sid`)"
|
|
50
|
+
lines << " KEY `idx_parent_id` (`_parent_id`)"
|
|
51
|
+
end
|
|
52
|
+
id_column = column_defs.find { |col| col.name == "id" }
|
|
53
|
+
# TEXT-family columns cannot carry a plain KEY without a prefix length.
|
|
54
|
+
if id_column && !id_column.sql_type.end_with?("TEXT")
|
|
55
|
+
lines << " KEY `idx_id` (`id`)"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
out.write("\n-- Table: #{name}\n")
|
|
59
|
+
out.write("DROP TABLE IF EXISTS #{SqlText.escape_identifier(name)};\n")
|
|
60
|
+
out.write(
|
|
61
|
+
"CREATE TABLE #{SqlText.escape_identifier(name)} (\n#{lines.join(",\n")}\n"
|
|
62
|
+
)
|
|
63
|
+
out.write(
|
|
64
|
+
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;\n"
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def write_batch(out, name, columns, value_rows)
|
|
69
|
+
return if value_rows.empty?
|
|
70
|
+
|
|
71
|
+
identifiers = columns.map { |column| SqlText.escape_identifier(column) }
|
|
72
|
+
out.write(
|
|
73
|
+
"INSERT INTO #{SqlText.escape_identifier(name)} (#{identifiers.join(", ")}) VALUES\n"
|
|
74
|
+
)
|
|
75
|
+
out.write(
|
|
76
|
+
value_rows.map { |values| " (#{values.join(", ")})" }.join(",\n")
|
|
77
|
+
)
|
|
78
|
+
out.write(";\n")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# PII manifest derived from x-pii annotations in the JSON Schemas, so
|
|
82
|
+
# downstream redaction tooling can query column sensitivity in MariaDB.
|
|
83
|
+
def write_meta_table(out, rows)
|
|
84
|
+
out.write("\n-- Table: _json_meta\n")
|
|
85
|
+
out.write("DROP TABLE IF EXISTS `_json_meta`;\n")
|
|
86
|
+
out.write(<<~SQL)
|
|
87
|
+
CREATE TABLE `_json_meta` (
|
|
88
|
+
`table_name` VARCHAR(64) NOT NULL,
|
|
89
|
+
`column_name` VARCHAR(64) NOT NULL,
|
|
90
|
+
`json_path` VARCHAR(255) NOT NULL,
|
|
91
|
+
`pii` TINYINT(1) NOT NULL DEFAULT 0,
|
|
92
|
+
`pii_note` TEXT,
|
|
93
|
+
`source_schema` VARCHAR(255),
|
|
94
|
+
PRIMARY KEY (`table_name`, `column_name`)
|
|
95
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
96
|
+
SQL
|
|
97
|
+
value_rows =
|
|
98
|
+
rows.map do |table, column, json_path, pii, note, source|
|
|
99
|
+
[
|
|
100
|
+
SqlText.escape_sql_string(table),
|
|
101
|
+
SqlText.escape_sql_string(column),
|
|
102
|
+
SqlText.escape_sql_string(json_path),
|
|
103
|
+
pii.to_s,
|
|
104
|
+
note.nil? ? "NULL" : SqlText.escape_sql_string(note),
|
|
105
|
+
SqlText.escape_sql_string(source)
|
|
106
|
+
]
|
|
107
|
+
end
|
|
108
|
+
write_batch(
|
|
109
|
+
out,
|
|
110
|
+
"_json_meta",
|
|
111
|
+
%w[table_name column_name json_path pii pii_note source_schema],
|
|
112
|
+
value_rows
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def format_value(value, column_def)
|
|
117
|
+
return format_null(column_def) if value.nil?
|
|
118
|
+
return SqlText.escape_sql_string(value.text) if value.is_a?(JsonValue)
|
|
119
|
+
|
|
120
|
+
case column_def&.kind
|
|
121
|
+
when :boolean
|
|
122
|
+
value == true ? "1" : "0"
|
|
123
|
+
when :integer, :float
|
|
124
|
+
if value.is_a?(Numeric)
|
|
125
|
+
value.to_s
|
|
126
|
+
else
|
|
127
|
+
SqlText.escape_sql_string(value.to_s)
|
|
128
|
+
end
|
|
129
|
+
when :datetime
|
|
130
|
+
format_datetime(value)
|
|
131
|
+
else
|
|
132
|
+
SqlText.escape_sql_string(value.to_s)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def row_safe_column_defs(column_defs, child:)
|
|
139
|
+
definitions = column_defs.map(&:dup)
|
|
140
|
+
inline_bytes = child ? 8 + 8 + 1_022 + 8 : 8
|
|
141
|
+
inline_bytes +=
|
|
142
|
+
definitions.sum { |column| inline_bytes_for(column.sql_type) }
|
|
143
|
+
return definitions if inline_bytes <= MAX_INLINE_ROW_BYTES
|
|
144
|
+
|
|
145
|
+
candidates =
|
|
146
|
+
definitions
|
|
147
|
+
.select { |column| column.sql_type.match?(/\AVARCHAR\(\d+\)\z/) }
|
|
148
|
+
.sort_by do |column|
|
|
149
|
+
width = inline_bytes_for(column.sql_type)
|
|
150
|
+
[
|
|
151
|
+
column.name == "id" ? 1 : 0,
|
|
152
|
+
column.null ? 0 : 1,
|
|
153
|
+
-width,
|
|
154
|
+
column.name
|
|
155
|
+
]
|
|
156
|
+
end
|
|
157
|
+
candidates.each do |column|
|
|
158
|
+
break if inline_bytes <= MAX_INLINE_ROW_BYTES
|
|
159
|
+
|
|
160
|
+
inline_bytes -= inline_bytes_for(column.sql_type) - TEXT_POINTER_BYTES
|
|
161
|
+
column.sql_type = "TEXT"
|
|
162
|
+
end
|
|
163
|
+
definitions
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def inline_bytes_for(sql_type)
|
|
167
|
+
if (match = sql_type.match(/\AVARCHAR\((\d+)\)\z/))
|
|
168
|
+
match[1].to_i * 4 + 2
|
|
169
|
+
elsif sql_type.end_with?("TEXT")
|
|
170
|
+
TEXT_POINTER_BYTES
|
|
171
|
+
elsif sql_type == "BIGINT" || sql_type == "DOUBLE"
|
|
172
|
+
8
|
|
173
|
+
elsif sql_type == "DATETIME"
|
|
174
|
+
5
|
|
175
|
+
else
|
|
176
|
+
1
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def format_null(column_def)
|
|
181
|
+
return "NULL" if column_def.nil? || column_def.null
|
|
182
|
+
|
|
183
|
+
case column_def.kind
|
|
184
|
+
when :integer, :float, :boolean
|
|
185
|
+
"0"
|
|
186
|
+
else
|
|
187
|
+
"''"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def format_datetime(value)
|
|
192
|
+
"'#{Time.iso8601(value.to_s).utc.strftime("%Y-%m-%d %H:%M:%S")}'"
|
|
193
|
+
rescue ArgumentError
|
|
194
|
+
SqlText.escape_sql_string(value.to_s)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|