sqlsnip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4691a62621199c780a25a27c14467e10ae3b48e2a20961a6f5660d5deab6b0aa
4
+ data.tar.gz: 7077d2b840173151db73d7bf038974791bbb5c4319e098d9741c2ef0b0b193b7
5
+ SHA512:
6
+ metadata.gz: 4b9f2117d5242c5535a434e036c0a0926f399c3ef0cb31d6deac57f8c187048900cbba9d5d06f649fcd9bbebff95e8e172efab9713262f7422fb3157269c1b6d
7
+ data.tar.gz: 455d2f47f9be97fdc91e5d8b0f909a0846e19af94c3ad028550c2428373aee4c8febf923ad35743916b0aadc2d4ce544effebb491b568427744fc196fd27ffa6
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.1.2
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sqlsnip.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Sqlsnip
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sqlsnip`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sqlsnip.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/sqlsnip ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shellopts'
4
+ require 'indented_io'
5
+
6
+ require 'sqlsnip.rb'
7
+
8
+ SPEC = %(
9
+ @ Pre-process a SQL snippet
10
+
11
+ -- FILE [START [STOP]]
12
+
13
+ Prepares a range of lines in a SQL file for execution by adding drop
14
+ statements for object defined in the range. If the range is absent, the whole
15
+ file is executed. Prints the resulting SQL script on standard output so it
16
+ can be piped to psql(1)
17
+
18
+ The range is given by a start and a stop line number (inclusive) and is
19
+ scanned for lines matching 'create' and a drop statement is added for each
20
+ match. Table, view, function, procedure, and trigger objects are supported.
21
+ 'or replace' definitions does not generate drop statements
22
+
23
+ The whole file is also scanned for 'set search_path' statements to determine
24
+ the current schema at the beginning of the snippet. If not found the schema
25
+ is determined from the file name. It is assumed that the file exists in a
26
+ prick(1) directory hierarchy
27
+
28
+ Note that the parser is very primitive: It expects the header of the
29
+ definition to be on one line. It also doesn't know about comments
30
+
31
+ EXAMPLE
32
+
33
+ sqlsnip can be used to create a vim macro in .vimrcc that executes the
34
+ selected text or the whole file:
35
+
36
+ \\function! ExecuteSqlFile() range
37
+ let path = expand('%:p')
38
+ let result = system('sqlsnip' . path . ' | psql')
39
+ echo result
40
+ endfunction
41
+
42
+ function! ExecuteSqlRange() range
43
+ let path = expand('%:p')
44
+ let start_line = line("'<")
45
+ let end_line = line("'>")
46
+ let result = system('sqlsnip ' . path . ' ' . start_line . ' ' . end_line . ' | psql')
47
+ echo result
48
+ endfunction
49
+
50
+ " CTRL+G - execute whole file
51
+ map <C-G> :call ExecuteSqlFile()<CR>
52
+
53
+ " CTRL-X - execute selected range
54
+ map <C-X> :call ExecuteSqlRange()<CR>
55
+ )
56
+
57
+ opts, args = ShellOpts.process(SPEC, ARGV)
58
+ file, start_line, stop_line = args.extract(1..3)
59
+ start_line ||= "1"
60
+ stop_line ||= "100000000"
61
+
62
+ prg = Sqlsnip::Prg.new(file, start_line.to_i, stop_line.to_i)
63
+ prg.run
64
+
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sqlsnip
4
+ VERSION = "0.1.0"
5
+ end
data/lib/sqlsnip.rb ADDED
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sqlsnip/version"
4
+
5
+ module Sqlsnip
6
+ class Error < StandardError; end
7
+
8
+ class Prg
9
+ UID_RE = /(?:[.\w]+)/
10
+ TABLE_MODIFIERS_RE = /(?:global|local|temporary|temp|unlogged)/
11
+ VIEW_MODIFIERS_RE = /(?:temp|temporary|recursive)/
12
+ IF_NOT_EXISTS_RE = /(?:if\s+not\s+exists)/
13
+ TRIGGER_MODIFIERS_RE = /(?:constraint)/
14
+
15
+ FUNCTION_ARGMODE_RE = /(?:in|out|inout|variadic)/
16
+ FUNCTION_ARGS_RE = /(?:#{FUNCTION_ARGMODE_RE}\s*,\s*)/
17
+ FUNCTION_DEFAULT_RE = /(?:default\s+.*)/
18
+
19
+ TABLE_RE = /(?:#{TABLE_MODIFIERS_RE}\s+)*table\s+(?:#{IF_NOT_EXISTS_RE}?\s+)?(#{UID_RE})/i
20
+ VIEW_RE = /(?:#{VIEW_MODIFIERS_RE}\s+)*view\s+(#{UID_RE})/i
21
+ FUNCTION_RE = /function\s+(#{UID_RE})\s*\((.*?)\)\s+(?:returns|$)/i
22
+ PROCEDURE_RE = /function\s+(#{UID_RE})\s*\((.*?)\)\s+(?:as|$)/i
23
+ TRIGGER_RE = /trigger\s+(#{UID_RE})\s+.*\s+on\s+(#{UID_RE})/i
24
+
25
+ DEFAULT_VALUE_RE = /(?:[^,\(]+(?:\([^\)]*\))?)/
26
+ DEFAULT_RE=/default\s+#{DEFAULT_VALUE_RE}/i
27
+
28
+ SEARCH_PATH_RE = /^\s*set\s+search_path/
29
+
30
+ attr_reader :file, :start_line, :stop_line
31
+ attr_reader :lines # The selected range of lines
32
+
33
+ def initialize(file, start_line, stop_line)
34
+ @file, @start_line, @stop_line = file, start_line, stop_line
35
+ File.exist?(@file) or raise Error, "Can't find #{file}"
36
+ @lines = []
37
+ @initial_search_path = nil
38
+ @project_dir = nil
39
+ end
40
+
41
+ def run
42
+ read_lines
43
+ stmts, has_search_path = generate_drop_statements
44
+ if !has_search_path
45
+ search_path = @initial_search_path
46
+ if search_path.nil?
47
+ schema = find_schema_from_file(file)
48
+ search_path = "set search_path to #{schema}"
49
+ end
50
+ stmts.unshift search_path
51
+ end
52
+
53
+ puts '\set ON_ERROR_STOP on'
54
+ puts stmts
55
+ puts
56
+ puts lines
57
+ end
58
+
59
+ private
60
+ attr_reader :initial_search_path, :project_dir
61
+
62
+ def read_lines
63
+ IO.readlines(file).each.with_index { |line, i|
64
+ i += 1
65
+ if i < start_line
66
+ @initial_search_path = line if line =~ /^\s*set\s+search_path/
67
+ elsif i <= stop_line
68
+ lines << line
69
+ else
70
+ break
71
+ end
72
+ }
73
+ end
74
+
75
+ def find_project_dir(path)
76
+ path = File.absolute_path(path)
77
+ while !File.exist?(File.join(path, "prick.yml"))
78
+ path != "/" or raise Error, "Can't find project directory"
79
+ path = File.dirname(path)
80
+ end
81
+ path
82
+ end
83
+
84
+ def find_schema_from_file(file)
85
+ path = File.dirname(file)
86
+ project_dir = find_project_dir(File.dirname(file))
87
+ path = path.delete_prefix(project_dir)
88
+ if path =~ /schema\/([^\/]+)/
89
+ schema = $1
90
+ else
91
+ schema != "" or raise Error, "Can't find schema from #{file}"
92
+ end
93
+ schema
94
+ end
95
+
96
+ def generate_drop_statements
97
+ has_search_path = false
98
+ stmts = []
99
+ for line in lines
100
+ case line
101
+ when /^\s*set\s+search_path/
102
+ sql = line
103
+ has_search_path = true if stmts.empty?
104
+ when /^\s*create\s+(.*)/
105
+ object = $1
106
+ case object
107
+ when TABLE_RE
108
+ table = $1
109
+ sql = "drop table if exists #{table} cascade;"
110
+ when VIEW_RE
111
+ view = $1
112
+ sql = "drop view if exists #{view} cascade;"
113
+ when FUNCTION_RE
114
+ function = $1
115
+ args_str = $2
116
+ # We assume that default values contain no commas
117
+ args = args_str.split(/\s*,\s*/).map { |arg|
118
+ arg.sub(/^#{UID_RE}\s+/, "").sub(/\s+#{FUNCTION_DEFAULT_RE}/, "")
119
+ }
120
+ sql = "drop function if exists #{function}(#{args.join(', ')}) cascade;"
121
+ when PROCEDURE_RE
122
+ procedure, args_str = $1, $2
123
+ # We assume that default values contain no commas
124
+ args = args_str.split(/\s*,\s*/).map { |arg|
125
+ arg.sub(/^#{UID_RE}\s+/, "").sub(/\s+#{FUNCTION_DEFAULT_RE}/, "")
126
+ }
127
+ sql = "drop procedure if exists #{procedure}(#{args.join(', ')}) cascade;"
128
+ when TRIGGER_RE
129
+ trigger, table = $1, $2
130
+ sql = "drop trigger if exists #{trigger} on #{table} cascade;"
131
+ else
132
+ next
133
+ end
134
+ else
135
+ next
136
+ end
137
+ stmts << sql
138
+ end
139
+ [stmts, has_search_path]
140
+ end
141
+ end
142
+ end
data/sig/sqlsnip.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Sqlsnip
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sqlsnip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Claus Rasmussen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: indented_io
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: shellopts
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Gem sqlsnip
42
+ email:
43
+ - claus.l.rasmussen@gmail.com
44
+ executables:
45
+ - sqlsnip
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".rspec"
50
+ - ".ruby-version"
51
+ - Gemfile
52
+ - README.md
53
+ - Rakefile
54
+ - exe/sqlsnip
55
+ - lib/sqlsnip.rb
56
+ - lib/sqlsnip/version.rb
57
+ - sig/sqlsnip.rbs
58
+ homepage: http://www.nowhere.com/
59
+ licenses: []
60
+ metadata:
61
+ homepage_uri: http://www.nowhere.com/
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.6.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.3.7
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Gem sqlsnip
81
+ test_files: []