Class: Tina4::SQLTranslator
- Inherits:
-
Object
- Object
- Tina4::SQLTranslator
- Defined in:
- lib/tina4/sql_translation.rb
Overview
Cross-engine SQL translator.
Each database adapter calls the rules it needs. Rules are composable and stateless – just string transforms.
Also includes query caching with TTL support.
Usage:
translated = Tina4::SQLTranslator.limit_to_rows("SELECT * FROM users LIMIT 10 OFFSET 5")
# => "SELECT * FROM users ROWS 6 TO 15"
Class Method Summary collapse
-
.auto_increment_syntax(sql, engine) ⇒ String
Translate AUTOINCREMENT across engines in DDL.
-
.boolean_to_int(sql) ⇒ String
Convert TRUE/FALSE to 1/0 for engines without boolean type.
-
.concat_pipes_to_func(sql) ⇒ String
Convert || concatenation to CONCAT() for MySQL/MSSQL.
-
.ilike_to_like(sql) ⇒ String
Convert ILIKE to LOWER() LIKE LOWER() for engines without ILIKE.
-
.limit_to_rows(sql) ⇒ String
Convert LIMIT/OFFSET to Firebird ROWS…TO syntax.
-
.limit_to_top(sql) ⇒ String
Convert LIMIT to MSSQL TOP syntax.
-
.placeholder_style(sql, style) ⇒ String
Convert ? placeholders to engine-specific style.
-
.query_key(sql, params = nil) ⇒ String
Generate a cache key for a query and its parameters.
Class Method Details
.auto_increment_syntax(sql, engine) ⇒ String
Translate AUTOINCREMENT across engines in DDL.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/tina4/sql_translation.rb', line 105 def auto_increment_syntax(sql, engine) case engine when "mysql" sql.gsub("AUTOINCREMENT", "AUTO_INCREMENT") when "postgresql" sql.gsub(/INTEGER\s+PRIMARY\s+KEY\s+AUTOINCREMENT/i, "SERIAL PRIMARY KEY") when "mssql" sql.gsub(/AUTOINCREMENT/i, "IDENTITY(1,1)") when "firebird" sql.gsub(/\s*AUTOINCREMENT\b/i, "") else sql end end |
.boolean_to_int(sql) ⇒ String
Convert TRUE/FALSE to 1/0 for engines without boolean type.
84 85 86 |
# File 'lib/tina4/sql_translation.rb', line 84 def boolean_to_int(sql) sql.gsub(/\bTRUE\b/i, "1").gsub(/\bFALSE\b/i, "0") end |
.concat_pipes_to_func(sql) ⇒ String
Convert || concatenation to CONCAT() for MySQL/MSSQL.
‘a’ || ‘b’ || ‘c’ => CONCAT(‘a’, ‘b’, ‘c’)
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/tina4/sql_translation.rb', line 69 def concat_pipes_to_func(sql) return sql unless sql.include?("||") parts = sql.split("||") if parts.length > 1 "CONCAT(#{parts.map(&:strip).join(', ')})" else sql end end |
.ilike_to_like(sql) ⇒ String
Convert ILIKE to LOWER() LIKE LOWER() for engines without ILIKE.
92 93 94 95 96 97 98 |
# File 'lib/tina4/sql_translation.rb', line 92 def ilike_to_like(sql) sql.gsub(/(\S+)\s+ILIKE\s+(\S+)/i) do col = ::Regexp.last_match(1).strip val = ::Regexp.last_match(2).strip "LOWER(#{col}) LIKE LOWER(#{val})" end end |
.limit_to_rows(sql) ⇒ String
Convert LIMIT/OFFSET to Firebird ROWS…TO syntax.
LIMIT 10 OFFSET 5 => ROWS 6 TO 15 LIMIT 10 => ROWS 1 TO 10
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/tina4/sql_translation.rb', line 27 def limit_to_rows(sql) # Try LIMIT X OFFSET Y first if (m = sql.match(/\bLIMIT\s+(\d+)\s+OFFSET\s+(\d+)\s*$/i)) limit = m[1].to_i offset = m[2].to_i start_row = offset + 1 end_row = offset + limit return sql[0...m.begin(0)] + "ROWS #{start_row} TO #{end_row}" end # Then try LIMIT X only if (m = sql.match(/\bLIMIT\s+(\d+)\s*$/i)) limit = m[1].to_i return sql[0...m.begin(0)] + "ROWS 1 TO #{limit}" end sql end |
.limit_to_top(sql) ⇒ String
Convert LIMIT to MSSQL TOP syntax.
SELECT … LIMIT 10 => SELECT TOP 10 … OFFSET queries are left unchanged (not supported by TOP).
53 54 55 56 57 58 59 60 61 |
# File 'lib/tina4/sql_translation.rb', line 53 def limit_to_top(sql) if (m = sql.match(/\bLIMIT\s+(\d+)\s*$/i)) && !sql.match?(/\bOFFSET\b/i) limit = m[1].to_i body = sql[0...m.begin(0)].strip return body.sub(/^(SELECT)\b/i, "\\1 TOP #{limit}") end sql end |
.placeholder_style(sql, style) ⇒ String
Convert ? placeholders to engine-specific style.
? => %s (MySQL, PostgreSQL) ? => :1, :2 (Oracle, Firebird)
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/tina4/sql_translation.rb', line 128 def placeholder_style(sql, style) case style when "%s" sql.gsub("?", "%s") when ":" count = 0 sql.chars.map do |ch| if ch == "?" count += 1 ":#{count}" else ch end end.join else sql end end |
.query_key(sql, params = nil) ⇒ String
Generate a cache key for a query and its parameters.
152 153 154 155 |
# File 'lib/tina4/sql_translation.rb', line 152 def query_key(sql, params = nil) raw = params ? "#{sql}|#{params.inspect}" : sql "query:#{Digest::SHA256.hexdigest(raw)}" end |