module Prick module Build class BuildBatch include Timer attr_reader :builder forward_to :builder, :conn def kind() @nodes.first&.kind end attr_reader :nodes def initialize(builder) @builder = builder @nodes = [] end def execute(**opts, &block) name = self.class.to_s.sub(/.*::/, "").split(/(?=[A-Z])/).map(&:downcase).join(" ") time "Execute #{name} (nodes: #{nodes.size})" do yield(**opts) end end def dump puts kind.upcase indent { nodes.each(&:dump) } end end class SqlBatch < BuildBatch def execute(step: true) # This is marginally faster! super { begin sql = [] file = nil node = nil # A SQL batch allows the first node to be an evaluated or executed node if nodes.first.is_a?(CommandNode) node = nodes.shift sql = [node.source] end if step conn.execute sql, silent: true if !sql.empty? for node in nodes conn.execute node.source, silent: true end else node = nil conn.execute sql + nodes[sql.size..-1].map(&:source), silent: true end rescue PG::Error => ex error, line, char = conn.err file = nil if step ; elsif line # Locate error node and make line number relative if line < nodes.first.lines node = nodes.first else line -= nodes.first.lines node = nodes[1..-1].find { |node| line -= 1 if line < node.lines line -= 2 true else line -= node.lines false end } end # file = node.path end if node.is_a?(SqlNode) message = ["#{error} in #{node.path}", line, char].compact.join(":") elsif node message = "#{error} from #{node.path}" else message = conn.errmsg + " in SQL batch" end raise PostgresError.new(message, file, line, char) end } end end class ModuleBatch < BuildBatch def execute super { nodes.each { |node| sql = node&.call conn.execute sql if sql } } end end class FoxBatch < BuildBatch # Inlining Fox instead of using command line program as it saves some deciseconds def execute t_type = t_fox = t_exe = nil super { timer = Timer.new "Execute fox batch (nodes: #{nodes.size}, clean: #{builder.clean})" t_type = Timer.new " Load data model" files = nodes.map(&:path) # Load meta object meta = PgMeta.new(conn, exclude_schemas: builder.pg_graph_ignore_schemas) # Create type object type = PgGraph::Type.new(meta, builder.reflections_file) # Timer t_type.stop t_fox = Timer.new " Parse files" # Create fox object fox = FixtureFox::Fox.new(type) # Parse files for file in files source, lines = fox.tokenize(file) fox.parse(source, lines) end # Analyze fox.assign_types fox.generate # Dump state file fox.write_state(FOX_STATE_PATH) # Timer t_fox.stop t_exe = Timer.new " Execute SQL" delete = builder.clean ? :none : :touched begin conn.execute fox.to_sql(format: :exec, delete: delete) # FIXME: Why only in FoxBatch - should be set higher up in the system conn.execute "update prick.versions set built_at = now() at time zone 'UTC'" rescue PG::SyntaxError, PG::Error => ex raise PostgresError.new(ex.message) end } t_type.emit t_fox.emit t_exe.emit end end end end