2
* $Id: PgDumpLoader.java 93 2008-08-03 12:31:08Z fordfrog $
4
package cz.startnet.utils.pgdiff.loader;
6
import cz.startnet.utils.pgdiff.parsers.AlterTableParser;
7
import cz.startnet.utils.pgdiff.parsers.CreateFunctionParser;
8
import cz.startnet.utils.pgdiff.parsers.CreateIndexParser;
9
import cz.startnet.utils.pgdiff.parsers.CreateSchemaParser;
10
import cz.startnet.utils.pgdiff.parsers.CreateSequenceParser;
11
import cz.startnet.utils.pgdiff.parsers.CreateTableParser;
12
import cz.startnet.utils.pgdiff.parsers.CreateTriggerParser;
13
import cz.startnet.utils.pgdiff.parsers.CreateViewParser;
14
import cz.startnet.utils.pgdiff.schema.PgDatabase;
16
import java.io.BufferedReader;
17
import java.io.FileInputStream;
18
import java.io.FileNotFoundException;
19
import java.io.IOException;
20
import java.io.InputStream;
21
import java.io.InputStreamReader;
22
import java.io.UnsupportedEncodingException;
24
import java.util.regex.Matcher;
25
import java.util.regex.Pattern;
28
* Loads PostgreSQL dump into classes.
31
* @version $Id: PgDumpLoader.java 93 2008-08-03 12:31:08Z fordfrog $
33
public class PgDumpLoader { //NOPMD
36
* Pattern for testing whether command is CREATE SCHEMA command.
38
private static final Pattern PATTERN_CREATE_SCHEMA =
40
"^CREATE[\\s]+SCHEMA[\\s]+.*$",
41
Pattern.CASE_INSENSITIVE);
43
* Pattern for parsing default schema (search_path).
45
private static final Pattern PATTERN_DEFAULT_SCHEMA =
47
"^SET[\\s]+search_path[\\s]*=[\\s]*([^,\\s]+)(?:,[\\s]+.*)?;$",
48
Pattern.CASE_INSENSITIVE);
50
* Pattern for testing whether command is CREATE TABLE command.
52
private static final Pattern PATTERN_CREATE_TABLE =
54
"^CREATE[\\s]+TABLE[\\s]+.*$",
55
Pattern.CASE_INSENSITIVE);
57
* Pattern for testing whether command is CREATE VIEW command.
59
private static final Pattern PATTERN_CREATE_VIEW =
61
"^CREATE[\\s]+(?:OR[\\s]+REPLACE[\\s]+)?VIEW[\\s]+.*$",
62
Pattern.CASE_INSENSITIVE);
64
* Pattern for testing whether command is ALTER TABLE command.
66
private static final Pattern PATTERN_ALTER_TABLE =
67
Pattern.compile("^ALTER[\\s]+TABLE[\\s]+.*$", Pattern.CASE_INSENSITIVE);
69
* Pattern for testing whether command is CREATE SEQUENCE command.
71
private static final Pattern PATTERN_CREATE_SEQUENCE =
73
"^CREATE[\\s]+SEQUENCE[\\s]+.*$",
74
Pattern.CASE_INSENSITIVE);
76
* Pattern for testing whether command is CREATE INDEX command.
78
private static final Pattern PATTERN_CREATE_INDEX =
80
"^CREATE[\\s]+(?:UNIQUE[\\s]+)?INDEX[\\s]+.*$",
81
Pattern.CASE_INSENSITIVE);
83
* Pattern for testing whether command is SET command.
85
private static final Pattern PATTERN_SET =
86
Pattern.compile("^SET[\\s]+.*$", Pattern.CASE_INSENSITIVE);
88
* Pattern for testing whether command is COMMENT command.
90
private static final Pattern PATTERN_COMMENT =
91
Pattern.compile("^COMMENT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
93
* Pattern for testing whether command is SELECT command.
95
private static final Pattern PATTERN_SELECT =
96
Pattern.compile("^SELECT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
98
* Pattern for testing whether command is INSERT INTO command.
100
private static final Pattern PATTERN_INSERT_INTO =
101
Pattern.compile("^INSERT[\\s]+INTO[\\s]+.*$", Pattern.CASE_INSENSITIVE);
103
* Pattern for testing whether command is REVOKE command.
105
private static final Pattern PATTERN_REVOKE =
106
Pattern.compile("^REVOKE[\\s]+.*$", Pattern.CASE_INSENSITIVE);
108
* Pattern for testing whether command is GRANT command.
110
private static final Pattern PATTERN_GRANT =
111
Pattern.compile("^GRANT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
113
* Pattern for testing whether command is CREATE TRIGGER command.
115
private static final Pattern PATTERN_CREATE_TRIGGER =
117
"^CREATE[\\s]+TRIGGER[\\s]+.*$",
118
Pattern.CASE_INSENSITIVE);
120
* Pattern for testing whether command is CREATE FUNCTION or CREATE
121
* OR REPLACE FUNCTION command.
123
private static final Pattern PATTERN_CREATE_FUNCTION =
125
"^CREATE[\\s]+(?:OR[\\s]+REPLACE[\\s]+)?FUNCTION[\\s]+.*$",
126
Pattern.CASE_INSENSITIVE);
128
* Pattern for getting the string that is used to end the function
129
* or the function definition itself.
131
private static final Pattern PATTERN_END_OF_FUNCTION =
133
"^(?:.*[\\s]+)?AS[\\s]+(['$][^\\s]*).*$",
134
Pattern.CASE_INSENSITIVE);
137
* Creates a new instance of PgDumpLoader.
139
private PgDumpLoader() {
144
* Loads database schema from dump file.
146
* @param inputStream input stream that should be read
147
* @param charsetName charset that should be used to read the file
149
* @return database schema from dump fle
151
* @throws UnsupportedOperationException Thrown if unsupported encoding has
153
* @throws FileException Thrown if problem occured while reading input
156
public static PgDatabase loadDatabaseSchema(final InputStream inputStream,
157
final String charsetName) { //NOPMD
159
final PgDatabase database = new PgDatabase();
160
BufferedReader reader = null;
164
new BufferedReader(new InputStreamReader(inputStream,
166
} catch (UnsupportedEncodingException ex) {
167
throw new UnsupportedOperationException("Unsupported encoding: " +
172
String line = reader.readLine();
174
while (line != null) {
175
line = stripComment(line).trim();
178
if (line.length() == 0) {
179
line = reader.readLine();
182
} else if (PATTERN_CREATE_SCHEMA.matcher(line).matches()) {
183
CreateSchemaParser.parse(
185
getWholeCommand(reader, line));
186
} else if (PATTERN_DEFAULT_SCHEMA.matcher(line).matches()) {
187
final Matcher matcher =
188
PATTERN_DEFAULT_SCHEMA.matcher(line);
190
database.setDefaultSchema(matcher.group(1));
191
} else if (PATTERN_CREATE_TABLE.matcher(line).matches()) {
192
CreateTableParser.parse(
194
getWholeCommand(reader, line));
195
} else if (PATTERN_ALTER_TABLE.matcher(line).matches()) {
196
AlterTableParser.parse(
198
getWholeCommand(reader, line));
199
} else if (PATTERN_CREATE_SEQUENCE.matcher(line).matches()) {
200
CreateSequenceParser.parse(
202
getWholeCommand(reader, line));
203
} else if (PATTERN_CREATE_INDEX.matcher(line).matches()) {
204
CreateIndexParser.parse(
206
getWholeCommand(reader, line));
207
} else if (PATTERN_CREATE_VIEW.matcher(line).matches()) {
208
CreateViewParser.parse(
210
getWholeCommand(reader, line));
211
} else if (PATTERN_CREATE_TRIGGER.matcher(line).matches()) {
212
CreateTriggerParser.parse(
214
getWholeCommand(reader, line));
215
} else if (PATTERN_CREATE_FUNCTION.matcher(line).matches()) {
216
CreateFunctionParser.parse(
218
getWholeFunction(reader, line));
219
} else if (PATTERN_SET.matcher(line).matches() || PATTERN_COMMENT.matcher(line).
220
matches() || PATTERN_SELECT.matcher(line).matches() || PATTERN_INSERT_INTO.matcher(line).
221
matches() || PATTERN_REVOKE.matcher(line).matches() || PATTERN_GRANT.matcher(line).
223
getWholeCommand(reader, line);
226
line = reader.readLine();
228
} catch (IOException ex) {
229
throw new FileException(FileException.CANNOT_READ_FILE, ex);
236
* Loads database schema from dump file.
238
* @param file name of file containing the dump
239
* @param charsetName charset that should be used to read the file
241
* @return database schema from dump file
243
* @throws FileException Thrown if file not found.
245
public static PgDatabase loadDatabaseSchema(final String file,
246
final String charsetName) {
248
return loadDatabaseSchema(new FileInputStream(file), charsetName);
249
} catch (FileNotFoundException ex) {
250
throw new FileException("File '" + file + "' not found", ex);
255
* Reads whole command from the reader into single-line string.
257
* @param reader reader to be read
258
* @param line first line read
260
* @return whole command from the reader into single-line string
262
* @throws FileException Thrown if problem occured while reading string
263
* from <code>reader</code>.
265
private static String getWholeCommand(
266
final BufferedReader reader,
268
String newLine = line.trim();
269
final StringBuilder sbCommand = new StringBuilder(newLine);
271
while (!newLine.trim().endsWith(";")) {
273
newLine = stripComment(reader.readLine()).trim();
274
} catch (IOException ex) {
275
throw new FileException(FileException.CANNOT_READ_FILE, ex);
278
if (newLine.length() > 0) {
279
sbCommand.append(' ');
280
sbCommand.append(newLine);
284
return sbCommand.toString();
288
* Reads whole CREATE FUNCTION DDL from the reader into multi-line
291
* @param reader reader to be read
292
* @param line first line read
294
* @return whole CREATE FUNCTION DDL from the reader into multi-line string
296
* @throws FileException Thrown if problem occured while reading string
297
* from<code>reader</code>.
298
* @throws RuntimeException Thrown if cannot find end of function.
300
private static String getWholeFunction(
301
final BufferedReader reader,
303
final String firstLine = line;
304
final StringBuilder sbCommand = new StringBuilder();
305
String newLine = line;
306
Pattern endOfFunctionPattern = null;
307
boolean searchForSemicolon = false;
309
while (newLine != null) {
310
if (!searchForSemicolon && (endOfFunctionPattern == null)) {
311
final Matcher matcher =
312
PATTERN_END_OF_FUNCTION.matcher(newLine);
314
if (matcher.matches()) {
315
String endOfFunction = matcher.group(1);
317
if (endOfFunction.charAt(0) == '\'') {
321
endOfFunction.substring(
323
endOfFunction.indexOf('$', 1) + 1);
326
if ("'".equals(endOfFunction)) {
327
endOfFunctionPattern =
329
"(?:.*[^\\\\]'.*|^.*[\\s]*'[\\s]*.*$)");
331
endOfFunctionPattern =
333
".*\\Q" + endOfFunction + "\\E.*$",
334
Pattern.CASE_INSENSITIVE);
337
final String stripped =
339
"[\\s]+AS[\\s]+\\Q" + endOfFunction + "\\E",
341
searchForSemicolon = endOfFunctionPattern.matcher(stripped).
346
sbCommand.append(newLine);
347
sbCommand.append('\n');
349
if (searchForSemicolon && newLine.trim().endsWith(";")) {
354
newLine = reader.readLine();
355
} catch (IOException ex) {
356
throw new FileException(FileException.CANNOT_READ_FILE, ex);
359
if (newLine == null) {
360
throw new RuntimeException(
361
"Cannot find end of function: " + firstLine);
364
if (!searchForSemicolon && (endOfFunctionPattern != null) && endOfFunctionPattern.matcher(newLine).
366
searchForSemicolon = true;
370
return sbCommand.toString();
374
* Strips comment from command line.
376
* @param command command
378
* @return if comment was found then command without the comment, otherwise
379
* the original command
381
private static String stripComment(final String command) {
382
String result = command;
383
int pos = result.indexOf("--");
393
for (int chr = 0; chr < pos; chr++) {
399
if ((count % 2) == 0) {
400
result = result.substring(0, pos).trim();
406
pos = result.indexOf("--", pos + 1);