~ubuntu-branches/ubuntu/karmic/apgdiff/karmic

« back to all changes in this revision

Viewing changes to src/main/java/cz/startnet/utils/pgdiff/loader/PgDumpLoader.java

  • Committer: Bazaar Package Importer
  • Author(s): Christoph Berg
  • Date: 2008-09-09 15:42:54 UTC
  • Revision ID: james.westby@ubuntu.com-20080909154254-458sv7ew1rczdal1
Tags: upstream-1.2
ImportĀ upstreamĀ versionĀ 1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id: PgDumpLoader.java 93 2008-08-03 12:31:08Z fordfrog $
 
3
 */
 
4
package cz.startnet.utils.pgdiff.loader;
 
5
 
 
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;
 
15
 
 
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;
 
23
 
 
24
import java.util.regex.Matcher;
 
25
import java.util.regex.Pattern;
 
26
 
 
27
/**
 
28
 * Loads PostgreSQL dump into classes.
 
29
 *
 
30
 * @author fordfrog
 
31
 * @version $Id: PgDumpLoader.java 93 2008-08-03 12:31:08Z fordfrog $
 
32
 */
 
33
public class PgDumpLoader { //NOPMD
 
34
 
 
35
    /**
 
36
     * Pattern for testing whether command is CREATE SCHEMA command.
 
37
     */
 
38
    private static final Pattern PATTERN_CREATE_SCHEMA =
 
39
        Pattern.compile(
 
40
        "^CREATE[\\s]+SCHEMA[\\s]+.*$",
 
41
        Pattern.CASE_INSENSITIVE);
 
42
    /**
 
43
     * Pattern for parsing default schema (search_path).
 
44
     */
 
45
    private static final Pattern PATTERN_DEFAULT_SCHEMA =
 
46
        Pattern.compile(
 
47
        "^SET[\\s]+search_path[\\s]*=[\\s]*([^,\\s]+)(?:,[\\s]+.*)?;$",
 
48
        Pattern.CASE_INSENSITIVE);
 
49
    /**
 
50
     * Pattern for testing whether command is CREATE TABLE command.
 
51
     */
 
52
    private static final Pattern PATTERN_CREATE_TABLE =
 
53
        Pattern.compile(
 
54
        "^CREATE[\\s]+TABLE[\\s]+.*$",
 
55
        Pattern.CASE_INSENSITIVE);
 
56
    /**
 
57
     * Pattern for testing whether command is CREATE VIEW command.
 
58
     */
 
59
    private static final Pattern PATTERN_CREATE_VIEW =
 
60
        Pattern.compile(
 
61
        "^CREATE[\\s]+(?:OR[\\s]+REPLACE[\\s]+)?VIEW[\\s]+.*$",
 
62
        Pattern.CASE_INSENSITIVE);
 
63
    /**
 
64
     * Pattern for testing whether command is ALTER TABLE command.
 
65
     */
 
66
    private static final Pattern PATTERN_ALTER_TABLE =
 
67
        Pattern.compile("^ALTER[\\s]+TABLE[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
68
    /**
 
69
     * Pattern for testing whether command is CREATE SEQUENCE command.
 
70
     */
 
71
    private static final Pattern PATTERN_CREATE_SEQUENCE =
 
72
        Pattern.compile(
 
73
        "^CREATE[\\s]+SEQUENCE[\\s]+.*$",
 
74
        Pattern.CASE_INSENSITIVE);
 
75
    /**
 
76
     * Pattern for testing whether command is CREATE INDEX command.
 
77
     */
 
78
    private static final Pattern PATTERN_CREATE_INDEX =
 
79
        Pattern.compile(
 
80
        "^CREATE[\\s]+(?:UNIQUE[\\s]+)?INDEX[\\s]+.*$",
 
81
        Pattern.CASE_INSENSITIVE);
 
82
    /**
 
83
     * Pattern for testing whether command is SET command.
 
84
     */
 
85
    private static final Pattern PATTERN_SET =
 
86
        Pattern.compile("^SET[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
87
    /**
 
88
     * Pattern for testing whether command is COMMENT command.
 
89
     */
 
90
    private static final Pattern PATTERN_COMMENT =
 
91
        Pattern.compile("^COMMENT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
92
    /**
 
93
     * Pattern for testing whether command is SELECT command.
 
94
     */
 
95
    private static final Pattern PATTERN_SELECT =
 
96
        Pattern.compile("^SELECT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
97
    /**
 
98
     * Pattern for testing whether command is INSERT INTO command.
 
99
     */
 
100
    private static final Pattern PATTERN_INSERT_INTO =
 
101
        Pattern.compile("^INSERT[\\s]+INTO[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
102
    /**
 
103
     * Pattern for testing whether command is REVOKE command.
 
104
     */
 
105
    private static final Pattern PATTERN_REVOKE =
 
106
        Pattern.compile("^REVOKE[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
107
    /**
 
108
     * Pattern for testing whether command is GRANT command.
 
109
     */
 
110
    private static final Pattern PATTERN_GRANT =
 
111
        Pattern.compile("^GRANT[\\s]+.*$", Pattern.CASE_INSENSITIVE);
 
112
    /**
 
113
     * Pattern for testing whether command is CREATE TRIGGER command.
 
114
     */
 
115
    private static final Pattern PATTERN_CREATE_TRIGGER =
 
116
        Pattern.compile(
 
117
        "^CREATE[\\s]+TRIGGER[\\s]+.*$",
 
118
        Pattern.CASE_INSENSITIVE);
 
119
    /**
 
120
     * Pattern for testing whether command is CREATE FUNCTION or CREATE
 
121
     * OR REPLACE FUNCTION command.
 
122
     */
 
123
    private static final Pattern PATTERN_CREATE_FUNCTION =
 
124
        Pattern.compile(
 
125
        "^CREATE[\\s]+(?:OR[\\s]+REPLACE[\\s]+)?FUNCTION[\\s]+.*$",
 
126
        Pattern.CASE_INSENSITIVE);
 
127
    /**
 
128
     * Pattern for getting the string that is used to end the function
 
129
     * or the function definition itself.
 
130
     */
 
131
    private static final Pattern PATTERN_END_OF_FUNCTION =
 
132
        Pattern.compile(
 
133
        "^(?:.*[\\s]+)?AS[\\s]+(['$][^\\s]*).*$",
 
134
        Pattern.CASE_INSENSITIVE);
 
135
 
 
136
    /**
 
137
     * Creates a new instance of PgDumpLoader.
 
138
     */
 
139
    private PgDumpLoader() {
 
140
        super();
 
141
    }
 
142
 
 
143
    /**
 
144
     * Loads database schema from dump file.
 
145
     *
 
146
     * @param inputStream input stream that should be read
 
147
     * @param charsetName charset that should be used to read the file
 
148
     *
 
149
     * @return database schema from dump fle
 
150
     *
 
151
     * @throws UnsupportedOperationException Thrown if unsupported encoding has
 
152
     *         been encountered.
 
153
     * @throws FileException Thrown if problem occured while reading input
 
154
     *         stream.
 
155
     */
 
156
    public static PgDatabase loadDatabaseSchema(final InputStream inputStream,
 
157
        final String charsetName) { //NOPMD
 
158
 
 
159
        final PgDatabase database = new PgDatabase();
 
160
        BufferedReader reader = null;
 
161
 
 
162
        try {
 
163
            reader =
 
164
                new BufferedReader(new InputStreamReader(inputStream,
 
165
                charsetName));
 
166
        } catch (UnsupportedEncodingException ex) {
 
167
            throw new UnsupportedOperationException("Unsupported encoding: " +
 
168
                charsetName, ex);
 
169
        }
 
170
 
 
171
        try {
 
172
            String line = reader.readLine();
 
173
 
 
174
            while (line != null) {
 
175
                line = stripComment(line).trim();
 
176
                line = line.trim();
 
177
 
 
178
                if (line.length() == 0) {
 
179
                    line = reader.readLine();
 
180
 
 
181
                    continue;
 
182
                } else if (PATTERN_CREATE_SCHEMA.matcher(line).matches()) {
 
183
                    CreateSchemaParser.parse(
 
184
                        database,
 
185
                        getWholeCommand(reader, line));
 
186
                } else if (PATTERN_DEFAULT_SCHEMA.matcher(line).matches()) {
 
187
                    final Matcher matcher =
 
188
                        PATTERN_DEFAULT_SCHEMA.matcher(line);
 
189
                    matcher.matches();
 
190
                    database.setDefaultSchema(matcher.group(1));
 
191
                } else if (PATTERN_CREATE_TABLE.matcher(line).matches()) {
 
192
                    CreateTableParser.parse(
 
193
                        database,
 
194
                        getWholeCommand(reader, line));
 
195
                } else if (PATTERN_ALTER_TABLE.matcher(line).matches()) {
 
196
                    AlterTableParser.parse(
 
197
                        database,
 
198
                        getWholeCommand(reader, line));
 
199
                } else if (PATTERN_CREATE_SEQUENCE.matcher(line).matches()) {
 
200
                    CreateSequenceParser.parse(
 
201
                        database,
 
202
                        getWholeCommand(reader, line));
 
203
                } else if (PATTERN_CREATE_INDEX.matcher(line).matches()) {
 
204
                    CreateIndexParser.parse(
 
205
                        database,
 
206
                        getWholeCommand(reader, line));
 
207
                } else if (PATTERN_CREATE_VIEW.matcher(line).matches()) {
 
208
                    CreateViewParser.parse(
 
209
                        database,
 
210
                        getWholeCommand(reader, line));
 
211
                } else if (PATTERN_CREATE_TRIGGER.matcher(line).matches()) {
 
212
                    CreateTriggerParser.parse(
 
213
                        database,
 
214
                        getWholeCommand(reader, line));
 
215
                } else if (PATTERN_CREATE_FUNCTION.matcher(line).matches()) {
 
216
                    CreateFunctionParser.parse(
 
217
                        database,
 
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).
 
222
                    matches()) {
 
223
                    getWholeCommand(reader, line);
 
224
                }
 
225
 
 
226
                line = reader.readLine();
 
227
            }
 
228
        } catch (IOException ex) {
 
229
            throw new FileException(FileException.CANNOT_READ_FILE, ex);
 
230
        }
 
231
 
 
232
        return database;
 
233
    }
 
234
 
 
235
    /**
 
236
     * Loads database schema from dump file.
 
237
     *
 
238
     * @param file name of file containing the dump
 
239
     * @param charsetName charset that should be used to read the file
 
240
     *
 
241
     * @return database schema from dump file
 
242
     *
 
243
     * @throws FileException Thrown if file not found.
 
244
     */
 
245
    public static PgDatabase loadDatabaseSchema(final String file,
 
246
        final String charsetName) {
 
247
        try {
 
248
            return loadDatabaseSchema(new FileInputStream(file), charsetName);
 
249
        } catch (FileNotFoundException ex) {
 
250
            throw new FileException("File '" + file + "' not found", ex);
 
251
        }
 
252
    }
 
253
 
 
254
    /**
 
255
     * Reads whole command from the reader into single-line string.
 
256
     *
 
257
     * @param reader reader to be read
 
258
     * @param line first line read
 
259
     *
 
260
     * @return whole command from the reader into single-line string
 
261
     *
 
262
     * @throws FileException Thrown if problem occured while reading string
 
263
     *         from <code>reader</code>.
 
264
     */
 
265
    private static String getWholeCommand(
 
266
        final BufferedReader reader,
 
267
        final String line) {
 
268
        String newLine = line.trim();
 
269
        final StringBuilder sbCommand = new StringBuilder(newLine);
 
270
 
 
271
        while (!newLine.trim().endsWith(";")) {
 
272
            try {
 
273
                newLine = stripComment(reader.readLine()).trim();
 
274
            } catch (IOException ex) {
 
275
                throw new FileException(FileException.CANNOT_READ_FILE, ex);
 
276
            }
 
277
 
 
278
            if (newLine.length() > 0) {
 
279
                sbCommand.append(' ');
 
280
                sbCommand.append(newLine);
 
281
            }
 
282
        }
 
283
 
 
284
        return sbCommand.toString();
 
285
    }
 
286
 
 
287
    /**
 
288
     * Reads whole CREATE FUNCTION DDL from the reader into multi-line
 
289
     * string.
 
290
     *
 
291
     * @param reader reader to be read
 
292
     * @param line first line read
 
293
     *
 
294
     * @return whole CREATE FUNCTION DDL from the reader into multi-line string
 
295
     *
 
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.
 
299
     */
 
300
    private static String getWholeFunction(
 
301
        final BufferedReader reader,
 
302
        final String line) {
 
303
        final String firstLine = line;
 
304
        final StringBuilder sbCommand = new StringBuilder();
 
305
        String newLine = line;
 
306
        Pattern endOfFunctionPattern = null;
 
307
        boolean searchForSemicolon = false;
 
308
 
 
309
        while (newLine != null) {
 
310
            if (!searchForSemicolon && (endOfFunctionPattern == null)) {
 
311
                final Matcher matcher =
 
312
                    PATTERN_END_OF_FUNCTION.matcher(newLine);
 
313
 
 
314
                if (matcher.matches()) {
 
315
                    String endOfFunction = matcher.group(1);
 
316
 
 
317
                    if (endOfFunction.charAt(0) == '\'') {
 
318
                        endOfFunction = "'";
 
319
                    } else {
 
320
                        endOfFunction =
 
321
                            endOfFunction.substring(
 
322
                            0,
 
323
                            endOfFunction.indexOf('$', 1) + 1);
 
324
                    }
 
325
 
 
326
                    if ("'".equals(endOfFunction)) {
 
327
                        endOfFunctionPattern =
 
328
                            Pattern.compile(
 
329
                            "(?:.*[^\\\\]'.*|^.*[\\s]*'[\\s]*.*$)");
 
330
                    } else {
 
331
                        endOfFunctionPattern =
 
332
                            Pattern.compile(
 
333
                            ".*\\Q" + endOfFunction + "\\E.*$",
 
334
                            Pattern.CASE_INSENSITIVE);
 
335
                    }
 
336
 
 
337
                    final String stripped =
 
338
                        newLine.replaceAll(
 
339
                        "[\\s]+AS[\\s]+\\Q" + endOfFunction + "\\E",
 
340
                        " ");
 
341
                    searchForSemicolon = endOfFunctionPattern.matcher(stripped).
 
342
                        matches();
 
343
                }
 
344
            }
 
345
 
 
346
            sbCommand.append(newLine);
 
347
            sbCommand.append('\n');
 
348
 
 
349
            if (searchForSemicolon && newLine.trim().endsWith(";")) {
 
350
                break;
 
351
            }
 
352
 
 
353
            try {
 
354
                newLine = reader.readLine();
 
355
            } catch (IOException ex) {
 
356
                throw new FileException(FileException.CANNOT_READ_FILE, ex);
 
357
            }
 
358
 
 
359
            if (newLine == null) {
 
360
                throw new RuntimeException(
 
361
                    "Cannot find end of function: " + firstLine);
 
362
            }
 
363
 
 
364
            if (!searchForSemicolon && (endOfFunctionPattern != null) && endOfFunctionPattern.matcher(newLine).
 
365
                matches()) {
 
366
                searchForSemicolon = true;
 
367
            }
 
368
        }
 
369
 
 
370
        return sbCommand.toString();
 
371
    }
 
372
 
 
373
    /**
 
374
     * Strips comment from command line.
 
375
     *
 
376
     * @param command command
 
377
     *
 
378
     * @return if comment was found then command without the comment, otherwise
 
379
     *         the original command
 
380
     */
 
381
    private static String stripComment(final String command) {
 
382
        String result = command;
 
383
        int pos = result.indexOf("--");
 
384
 
 
385
        while (pos >= 0) {
 
386
            if (pos == 0) {
 
387
                result = "";
 
388
 
 
389
                break;
 
390
            } else {
 
391
                int count = 0;
 
392
 
 
393
                for (int chr = 0; chr < pos; chr++) {
 
394
                    if (chr == '\'') {
 
395
                        count++;
 
396
                    }
 
397
                }
 
398
 
 
399
                if ((count % 2) == 0) {
 
400
                    result = result.substring(0, pos).trim();
 
401
 
 
402
                    break;
 
403
                }
 
404
            }
 
405
 
 
406
            pos = result.indexOf("--", pos + 1);
 
407
        }
 
408
 
 
409
        return result;
 
410
    }
 
411
}