1 """
2 Support code for attaching scripts to objects.
3
4 Scripts can be either in python or in SQL. They always live on
5 make instances. For details, see Scripting in the reference
6 documentation.
7 """
8
9
10
11
12
13
14
15 from pyparsing import (
16 OneOrMore, ZeroOrMore, QuotedString, Forward,
17 SkipTo, StringEnd, Regex, Suppress,
18 Literal)
19
20 from gavo import base
21 from gavo import utils
22 from gavo.base import sqlsupport
23 from gavo.rscdef import rmkfuncs
24
25
28
29
31 """returns a pyparsing ParserElement that splits SQL scripts into
32 individual commands.
33
34 The rules are: Statements are separated by semicolons, empty statements
35 are allowed.
36 """
37 with utils.pyparsingWhitechars(" \t"):
38 atom = Forward()
39 atom.setName("Atom")
40
41 sqlComment = Literal("--")+SkipTo("\n", include=True)
42 cStyleComment = Literal("/*")+SkipTo("*/", include=True)
43 comment = sqlComment | cStyleComment
44 lineEnd = Literal("\n")
45
46 simpleStr = QuotedString(quoteChar="'", escChar="\\",
47 multiline=True, unquoteResults=False)
48 quotedId = QuotedString(quoteChar='"', escChar="\\", unquoteResults=False)
49 dollarQuoted = Regex(r"(?s)\$(\w*)\$.*?\$\1\$")
50 dollarQuoted.setName("dollarQuoted")
51
52
53 strLiteral = simpleStr | dollarQuoted | quotedId
54 strLiteral.setName("strLiteral")
55
56 other = Regex("[^;'\"$]+")
57 other.setName("other")
58
59 literalDollar = Literal("$") + ~ Literal("$")
60 statementEnd = ( Literal(';') + ZeroOrMore(lineEnd) | StringEnd() )
61
62 atom << ( Suppress(comment) | other | strLiteral | literalDollar )
63 statement = OneOrMore(atom) + Suppress( statementEnd )
64 statement.setName("statement")
65 statement.setParseAction(lambda s, p, toks: " ".join(toks))
66
67 script = OneOrMore( statement ) + StringEnd()
68 script.setName("script")
69 script.setParseAction(lambda s, p, toks: [t for t in toks.asList()
70 if str(t).strip()])
71
72 if False:
73 atom.setDebug(True)
74 comment.setDebug(True)
75 other.setDebug(True)
76 strLiteral.setDebug(True)
77 statement.setDebug(True)
78 statementEnd.setDebug(True)
79 dollarQuoted.setDebug(True)
80 literalDollar.setDebug(True)
81 return script
82
83
84 getSQLScriptGrammar = utils.CachedGetter(_getSQLScriptGrammar)
85
86
88 """An object encapsulating the preparation and execution of
89 scripts.
90
91 They are constructed with instances of Script below and have
92 a method run(dbTable, **kwargs).
93
94 You probably should not override __init__ but instead override
95 _prepare(script) which is called by __init__.
96 """
100
102 raise ValueError("Cannot instantate plain ScriptRunners")
103
104
106 """A runner for SQL scripts.
107
108 These will always use the table's querier to execute the statements.
109
110 Keyword arguments to run are ignored.
111 """
115
116 - def run(self, dbTable, **kwargs):
117 for statement in self.statements:
118 dbTable.query(statement.replace("%", "%%"))
119
120
122 """A runner for "autocommitted" SQL scripts.
123
124 These are like SQLScriptRunners, except that for every statement,
125 a savepoint is created, and for SQL errors, the savepoint is restored
126 (in other words ACSQL scripts turn SQL errors into warnings).
127 """
128 - def run(self, dbTable, **kwargs):
129 for statement in self.statements:
130 try:
131 dbTable.query("SAVEPOINT beforeStatement")
132 try:
133 dbTable.query(statement.replace("%", "%%"))
134 except sqlsupport.DBError as msg:
135 dbTable.query("ROLLBACK TO SAVEPOINT beforeStatement")
136 base.ui.notifyError("Ignored error during script execution: %s"%
137 msg)
138 finally:
139 dbTable.query("RELEASE SAVEPOINT beforeStatement")
140
141
143 """A runner for python scripts.
144
145 The scripts can access the current table as table (and thus run
146 SQL statements through table.query(query, pars)).
147
148 Additional keyword arguments are available under their names.
149
150 You are in the namespace of usual procApps (like procs, rowgens, and
151 the like).
152 """
159
163
164 - def _prepare(self, script, moreNames={}):
165 self.scriptFun = self._compile()
166
167 - def run(self, dbTable, **kwargs):
168
169
170
171
172 if kwargs:
173 func = self._compile(kwargs)
174 else:
175 func = self.scriptFun
176 func(dbTable, **kwargs)
177
178
179 RUNNER_CLASSES = {
180 "SQL": SQLScriptRunner,
181 "python": PythonScriptRunner,
182 "AC_SQL": ACSQLScriptRunner,
183 }
184
185 -class Script(base.Structure, base.RestrictionMixin):
186 """A script, i.e., some executable item within a resource descriptor.
187
188 The content of scripts is given by their type -- usually, they are
189 either python scripts or SQL with special rules for breaking the
190 script into individual statements (which are basically like python's).
191
192 The special language AC_SQL is like SQL, but execution errors are
193 ignored. This is not what you want for most data RDs (it's intended
194 for housekeeping scripts).
195
196 See `Scripting`_.
197 """
198 name_ = "script"
199 typeDesc_ = "Embedded executable code with a type definition"
200
201 _lang = base.EnumeratedUnicodeAttribute("lang", default=base.Undefined,
202 description="Language of the script.",
203 validValues=["SQL", "python", "AC_SQL"], copyable=True)
204 _type = base.EnumeratedUnicodeAttribute("type", default=base.Undefined,
205 description="Point of time at which script is to run.",
206 validValues=["preImport", "newSource", "preIndex", "preCreation",
207 "postCreation",
208 "beforeDrop", "sourceDone"], copyable=True)
209 _name = base.UnicodeAttribute("name", default="anonymous",
210 description="A human-consumable designation of the script.",
211 copyable=True)
212 _notify = base.BooleanAttribute("notify", default=True,
213 description="Send out a notification when running this"
214 " script.", copyable=True)
215 _content = base.DataContent(copyable=True, description="The script body.")
216 _original = base.OriginalAttribute()
217
222
223
224
226 """A mixin that gives objects a getRunner method and a script attribute.
227
228 Within the DC, this is only mixed into make.
229
230 The getRunner() method returns a callable that takes the current table
231 (we expect db tables, really), the phase and possibly further keyword
232 arguments, as appropriate for the phase.
233
234 Objects mixing this in must also support define a method
235 getExpander() returning an object mixin in a MacroPackage.
236 """
237 _scripts = base.StructListAttribute("scripts", childFactory=Script,
238 description="Code snippets attached to this object. See Scripting_ .",
239 copyable=True)
240
253
254 self._runScriptsCache = runScripts
255
256 return self._runScriptsCache
257