Class: Exwiw::Adapter::SqliteAdapter
- Includes:
- SqlBulkInsert
- Defined in:
- lib/exwiw/adapter/sqlite_adapter.rb
Defined Under Namespace
Classes: StreamingResult
Constant Summary
Constants included from SqlBulkInsert
Exwiw::Adapter::SqlBulkInsert::STREAM_FLUSH_ROWS
Instance Attribute Summary
Attributes inherited from Base
Instance Method Summary collapse
- #build_query(table, dump_target, table_by_name) ⇒ Object
- #compile_ast(query_ast, count_only: false) ⇒ Object
- #dump_schema(ordered_tables, output_path) ⇒ Object
- #execute(query_ast) ⇒ Object
- #explain(query_ast) ⇒ Object
- #to_bulk_delete(select_query_ast, table) ⇒ Object
Methods included from SqlBulkInsert
#to_bulk_insert, #write_inserts
Methods inherited from Base
#commented_sql, #default_bulk_insert_chunk_size, #describe_query, #dumpable?, #initialize, #output_extension, #post_insert_sql, #pre_insert_sql, #query_comment_text, #schema_output_extension, #sql_query_comment, #supports_bulk_delete?, table_config_class, #to_copy_from_stdin, #validate_as_dump_target!, #write_inserts
Constructor Details
This class inherits a constructor from Exwiw::Adapter::Base
Instance Method Details
#build_query(table, dump_target, table_by_name) ⇒ Object
59 60 61 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 59 def build_query(table, dump_target, table_by_name) Exwiw::QueryAstBuilder.run(table.name, table_by_name, dump_target, @logger) end |
#compile_ast(query_ast, count_only: false) ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 198 def compile_ast(query_ast, count_only: false) raise NotImplementedError unless query_ast.is_a?(Exwiw::QueryAst::Select) sql = "SELECT " sql += if count_only "COUNT(*)" elsif query_ast.select_all "*" else query_ast.columns.map { |col| compile_column_name(query_ast, col) }.join(', ') end sql += " FROM #{query_ast.from_table_name}" query_ast.join_clauses.each do |join| sql += " JOIN #{join.join_table_name} ON #{join.base_table_name}.#{join.foreign_key} = #{join.join_table_name}.#{join.primary_key}" join.where_clauses.each do |where| compiled_where_condition = compile_where_condition(where, join.join_table_name) sql += " AND #{compiled_where_condition}" end # base_where_clauses is compiled against the joined-from table # (base_table_name), e.g. the type-column filter on a polymorphic # source table. join.base_where_clauses.each do |where| compiled_where_condition = compile_where_condition(where, join.base_table_name) sql += " AND #{compiled_where_condition}" end end if query_ast.where_clauses.any? sql += " WHERE " sql += query_ast.where_clauses.map { |where| compile_where_condition(where, query_ast.from_table_name) }.join(' AND ') end sql end |
#dump_schema(ordered_tables, output_path) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 82 def dump_schema(ordered_tables, output_path) @logger.debug(" Reading schema from sqlite_master...") target_names = ordered_tables.map(&:name) # `sqlite_master` row order preserves table creation order, which is also # the dependency order produced by ActiveRecord-style migrations. To respect # the caller-provided order, we partition tables / their owned indexes by # ordered_tables. all = connection.execute(<<~SQL) SELECT type, name, tbl_name, sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%' SQL tables_by_name = all.select { |type, _, _, _| type == 'table' }.to_h { |_, name, _, sql| [name, sql] } indexes_by_owner = all.select { |type, _, _, _| type == 'index' }.group_by { |_, _, tbl, _| tbl } triggers_by_owner = all.select { |type, _, _, _| type == 'trigger' }.group_by { |_, _, tbl, _| tbl } statements = [] target_names.each do |name| table_sql = tables_by_name[name] next unless table_sql statements << finalize_stmt(DdlPostprocessor.add_if_not_exists_to_create_table(table_sql.strip)) (indexes_by_owner[name] || []).each do |_, _, _, idx_sql| statements << finalize_stmt(DdlPostprocessor.add_if_not_exists_to_create_index(idx_sql.strip)) end (triggers_by_owner[name] || []).each do |_, _, _, trg_sql| statements << finalize_stmt(trg_sql.strip) end end File.open(output_path, 'w') do |file| file.puts("-- Auto-generated by exwiw. Idempotent CREATE statements for SQLite.") file.puts(statements.join("\n")) end @logger.info(" Wrote #{statements.size} schema statement(s) to #{output_path}.") end |
#execute(query_ast) ⇒ Object
63 64 65 66 67 68 69 70 71 72 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 63 def execute(query_ast) data_sql = commented_sql(query_ast) # Count via the same FROM/JOIN/WHERE (projection replaced by COUNT(*)) so # the Runner can skip empty tables and log the row count without draining # the cursor. See StreamingResult for why this is exact. count_sql = "#{sql_query_comment(query_ast)} #{compile_ast(query_ast, count_only: true)}" @logger.debug(" Executing SQL (cursor stream): \n#{data_sql}") StreamingResult.new(connection: connection, data_sql: data_sql, count_sql: count_sql) end |
#explain(query_ast) ⇒ Object
74 75 76 77 78 79 80 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 74 def explain(query_ast) sql = commented_sql(query_ast) @logger.debug(" Executing EXPLAIN QUERY PLAN: \n#{sql}") rows = connection.execute("EXPLAIN QUERY PLAN #{sql}") rows.map { |row| row[3] }.join("\n") end |
#to_bulk_delete(select_query_ast, table) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/exwiw/adapter/sqlite_adapter.rb', line 136 def to_bulk_delete(select_query_ast, table) raise NotImplementedError unless select_query_ast.is_a?(Exwiw::QueryAst::Select) sql = "DELETE FROM #{select_query_ast.from_table_name}" if select_query_ast.join_clauses.empty? # Ignore filter option, because bulk delete is for cleaning before import, # so it should delete all records to avoid foreign key violation & data consistancy. compiled_where_conditions = select_query_ast. where_clauses. select { |where| where.is_a?(Exwiw::QueryAst::WhereClause) }. map do |where| compile_where_condition(where, select_query_ast.from_table_name) end if compiled_where_conditions.size > 0 sql += "\nWHERE " sql += compiled_where_conditions.join(' AND ') end sql += ";" return sql end subquery_ast = Exwiw::QueryAst::Select.new first_join = select_query_ast.join_clauses.first.clone subquery_ast.from(first_join.join_table_name) primay_key_col = table.columns.find { |col| col.name == table.primary_key } subquery_ast.select([primay_key_col]) select_query_ast.join_clauses[1..].each do |join| subquery_ast.join(join) end first_join.where_clauses.each do |where| # Ignore filter option, because bulk delete is for cleaning before import, # so it should delete all records to avoid foreign key violation & data consistancy. subquery_ast.where(where) if where.is_a?(Exwiw::QueryAst::WhereClause) end foreign_key = first_join.foreign_key subquery_sql = compile_ast(subquery_ast) sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})" # first_join.base_where_clauses holds conditions on the outer # delete-target table (from_table_name), such as a polymorphic type # column. They are not part of the subquery, so add them to the outer # WHERE. This prevents deleting rows that belong to a different # polymorphic type. first_join.base_where_clauses.each do |where| next unless where.is_a?(Exwiw::QueryAst::WhereClause) sql += " AND #{compile_where_condition(where, select_query_ast.from_table_name)}" end sql += ";" sql end |