Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
tree: 8e24b3195b
Fetching contributors…

Cannot retrieve contributors at this time

executable file 191 lines (151 sloc) 6.133 kb
#!/usr/bin/env python
"""Generate "ALTER EXTENSION" statements to package a list of SQL definitions.
The script doesn't try to be a robust parser: it relies on the input file
being regular enough.
The script is also incomplete, but it complains loudly if it meets elements
it doesn't know how to deal with.
"""
# Copyright (c) 2011, Daniele Varrazzo <daniele.varrazzo@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * The name of Daniele Varrazzo may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import re
import sys
re_stmt = re.compile(
r'CREATE\s+(?:OR\s+REPLACE\s+)?(\w+)\b([^;]+);',
re.MULTILINE | re.IGNORECASE)
def process_file(f, opt):
data = f.read()
# Clean up parts we don't care about and that make parsing more complex
data = strip_comments(data)
data = strip_strings(data)
for m in re_stmt.finditer(data):
try:
f = globals()['process_' + m.group(1).lower()]
except:
# TODO: all the missing statements
raise KeyError("can't process statement 'CREATE %s'" %
(m.group(1).upper(),))
f(m.group(2), opt.extname)
def process_aggregate(body, extname):
# TODO: parse the "old syntax"
name = _find_name(body)
args = _find_args(body)
print "ALTER EXTENSION %s ADD AGGREGATE %s %s;" % (
extname, name, args)
def process_cast(body, extname):
args = _find_args(body)
print "ALTER EXTENSION %s ADD CAST %s;" % (
extname, args)
def process_function(body, extname):
name = _find_name(body)
args = _find_args(body)
print "ALTER EXTENSION %s ADD FUNCTION %s %s;" % (
extname, name, args)
def process_operator(body, extname):
if body.lstrip().lower().startswith('class'):
return process_operator_class(body, extname)
m = re.match(r'^\s*([^\s\(]+)\s*\(', body)
if m is None:
raise ValueError("can't find operator:\n%s" % body)
op = m.group(1)
m = re.search(r'LEFTARG\s*=\s*([^,\)]+)', body, re.IGNORECASE)
larg = m and m.group(1).strip()
m = re.search(r'RIGHTARG\s*=\s*([^,\)]+)', body, re.IGNORECASE)
rarg = m and m.group(1).strip()
if not (larg or rarg):
raise ValueError("can't find operator arguments:\n%s" % body)
print "ALTER EXTENSION %s ADD OPERATOR %s (%s, %s);" % (
extname, op, larg or 'NONE', rarg or 'NONE')
def process_operator_class(body, extname):
m = re.match(r'^\s*CLASS\s*(\w+)\b.*?USING\s+(\w+)\b',
body, re.IGNORECASE | re.DOTALL)
if m is None:
raise ValueError("can't parse operator class:\n%s" % body)
print "ALTER EXTENSION %s ADD OPERATOR CLASS %s USING %s;" % (
extname, m.group(1), m.group(2))
def process_type(body, extname):
name = _find_name(body)
print "ALTER EXTENSION %s ADD TYPE %s;" % (
extname, name)
re_name = re.compile(r'^\s*(\w+)\b')
def _find_name(body):
m = re_name.match(body)
if m is None:
raise ValueError("can't find name:\n%s" % body)
return m.group(1)
def _find_args(body):
# find the closing brace of the arguments list
# count the braces to avoid getting fooled by type modifiers
# e.g. varchar(10)
count = 0
for i, c in enumerate(body):
if c == '(':
count += 1
elif c == ')':
count -= 1
if count == 0:
break
else:
raise ValueError("failed to parse arguments list:\n%s")
astart = body.index('(')
aend = i + 1
return ' '.join(body[astart:aend].split())
re_comment_single = re.compile(r'--.*?$', re.MULTILINE)
re_comment_multi = re.compile(r'/\*.*?\*/', re.DOTALL)
def strip_comments(s):
"""Remove SQL comments from a string.
TODO: doesn't handle nested comments.
"""
s = re_comment_single.sub("''", s)
s = re_comment_multi.sub("''", s)
return s
re_string_quote = re.compile(r"'(''|[^'])*'")
re_string_dollar = re.compile(r'\$([^$]*)\$.*?\$\1\$')
def strip_strings(s):
"""Replace all the SQL literal strings with the empty string."""
s = re_string_quote.sub('', s)
s = re_string_dollar.sub('', s)
return s
def main():
opt = parse_options()
print "-- This file was automatically generated"
print "-- by the script '%s'" % __file__
print "-- from input files:", ", ".join(opt.filenames)
print
for fn in opt.filenames:
f = fn == '-' and sys.stdin or open(fn)
process_file(f, opt)
def parse_options():
from optparse import OptionParser
parser = OptionParser()
parser.add_option('--extname')
opt, args = parser.parse_args()
if not opt.extname:
parser.error("extension name must be specified")
opt.filenames = args or ['-']
return opt
if __name__ == '__main__':
main()
Jump to Line
Something went wrong with that request. Please try again.