1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.graph.query.validate;
25
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import net.jcip.annotations.Immutable;
34 import net.jcip.annotations.NotThreadSafe;
35 import org.modeshape.common.text.ParsingException;
36 import org.modeshape.common.util.CheckArg;
37 import org.modeshape.graph.GraphI18n;
38 import org.modeshape.graph.query.QueryContext;
39 import org.modeshape.graph.query.model.QueryCommand;
40 import org.modeshape.graph.query.model.SelectorName;
41 import org.modeshape.graph.query.model.TypeSystem;
42 import org.modeshape.graph.query.model.Visitors;
43 import org.modeshape.graph.query.parse.InvalidQueryException;
44 import org.modeshape.graph.query.parse.SqlQueryParser;
45 import org.modeshape.graph.query.plan.CanonicalPlanner;
46 import org.modeshape.graph.query.plan.PlanHints;
47 import org.modeshape.graph.query.plan.PlanNode;
48 import org.modeshape.graph.query.plan.PlanNode.Property;
49 import org.modeshape.graph.query.plan.PlanNode.Type;
50
51
52
53
54 @Immutable
55 public class ImmutableSchemata implements Schemata {
56
57
58
59
60
61
62
63
64 public static Builder createBuilder( TypeSystem typeSystem ) {
65 CheckArg.isNotNull(typeSystem, "typeSystem");
66 return new Builder(typeSystem);
67 }
68
69
70
71
72 @NotThreadSafe
73 public static class Builder {
74
75 private final TypeSystem typeSystem;
76 private final Map<SelectorName, ImmutableTable> tables = new HashMap<SelectorName, ImmutableTable>();
77 private final Map<SelectorName, QueryCommand> viewDefinitions = new HashMap<SelectorName, QueryCommand>();
78 private final Set<SelectorName> tablesOrViewsWithExtraColumns = new HashSet<SelectorName>();
79
80 protected Builder( TypeSystem typeSystem ) {
81 this.typeSystem = typeSystem;
82 }
83
84
85
86
87
88
89
90
91
92
93
94 public Builder addTable( String name,
95 String... columnNames ) {
96 CheckArg.isNotEmpty(name, "name");
97 CheckArg.isNotEmpty(columnNames, "columnNames");
98 List<Column> columns = new ArrayList<Column>();
99 int i = 0;
100 for (String columnName : columnNames) {
101 CheckArg.isNotEmpty(columnName, "columnName[" + (i++) + "]");
102 columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType()));
103 }
104 ImmutableTable table = new ImmutableTable(new SelectorName(name), columns, false);
105 tables.put(table.getName(), table);
106 return this;
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120 public Builder addTable( String name,
121 String[] columnNames,
122 String[] types ) {
123 CheckArg.isNotEmpty(name, "name");
124 CheckArg.isNotEmpty(columnNames, "columnNames");
125 CheckArg.isNotEmpty(types, "types");
126 CheckArg.isEquals(columnNames.length, "columnNames.length", types.length, "types.length");
127 List<Column> columns = new ArrayList<Column>();
128 assert columnNames.length == types.length;
129 for (int i = 0; i != columnNames.length; ++i) {
130 String columnName = columnNames[i];
131 CheckArg.isNotEmpty(columnName, "columnName[" + i + "]");
132 columns.add(new ImmutableColumn(columnName, types[i]));
133 }
134 ImmutableTable table = new ImmutableTable(new SelectorName(name), columns, false);
135 tables.put(table.getName(), table);
136 return this;
137 }
138
139
140
141
142
143
144
145
146
147
148
149 public Builder addView( String name,
150 String definition ) {
151 CheckArg.isNotEmpty(name, "name");
152 CheckArg.isNotEmpty(definition, "definition");
153 SqlQueryParser parser = new SqlQueryParser();
154 QueryCommand command = parser.parseQuery(definition, typeSystem);
155 this.viewDefinitions.put(new SelectorName(name), command);
156 return this;
157 }
158
159
160
161
162
163
164
165
166
167
168 public Builder addView( String name,
169 QueryCommand definition ) {
170 CheckArg.isNotEmpty(name, "name");
171 CheckArg.isNotNull(definition, "definition");
172 this.viewDefinitions.put(new SelectorName(name), definition);
173 return this;
174 }
175
176
177
178
179
180
181
182
183
184
185
186
187 public Builder addColumn( String tableName,
188 String columnName,
189 String type ) {
190 CheckArg.isNotEmpty(tableName, "tableName");
191 CheckArg.isNotEmpty(columnName, "columnName");
192 CheckArg.isNotNull(type, "type");
193 return addColumn(tableName, columnName, type, ImmutableColumn.DEFAULT_FULL_TEXT_SEARCHABLE);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public Builder addColumn( String tableName,
209 String columnName,
210 String type,
211 boolean fullTextSearchable ) {
212 CheckArg.isNotEmpty(tableName, "tableName");
213 CheckArg.isNotEmpty(columnName, "columnName");
214 CheckArg.isNotNull(type, "type");
215 SelectorName selector = new SelectorName(tableName);
216 ImmutableTable existing = tables.get(selector);
217 ImmutableTable table = null;
218 if (existing == null) {
219 List<Column> columns = new ArrayList<Column>();
220 columns.add(new ImmutableColumn(columnName, type, fullTextSearchable));
221 table = new ImmutableTable(selector, columns, false);
222 } else {
223 table = existing.withColumn(columnName, type, fullTextSearchable);
224 }
225 tables.put(table.getName(), table);
226 return this;
227 }
228
229
230
231
232
233
234
235
236
237 public Builder makeSearchable( String tableName,
238 String columnName ) {
239 CheckArg.isNotEmpty(tableName, "tableName");
240 CheckArg.isNotEmpty(columnName, "columnName");
241 SelectorName selector = new SelectorName(tableName);
242 ImmutableTable existing = tables.get(selector);
243 ImmutableTable table = null;
244 if (existing == null) {
245 List<Column> columns = new ArrayList<Column>();
246 columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true));
247 table = new ImmutableTable(selector, columns, false);
248 } else {
249 Column column = existing.getColumn(columnName);
250 String type = typeSystem.getDefaultType();
251 if (column != null) {
252 type = column.getPropertyType();
253 }
254 table = existing.withColumn(columnName, type, true);
255 }
256 tables.put(table.getName(), table);
257 return this;
258 }
259
260
261
262
263
264
265
266
267 public Builder markExtraColumns( String tableName ) {
268 CheckArg.isNotEmpty(tableName, "tableName");
269 SelectorName selector = new SelectorName(tableName);
270 tablesOrViewsWithExtraColumns.add(selector);
271 return this;
272 }
273
274
275
276
277
278
279
280
281
282
283 public Builder addKey( String tableName,
284 String... columnNames ) {
285 CheckArg.isNotEmpty(tableName, "tableName");
286 CheckArg.isNotEmpty(columnNames, "columnNames");
287 ImmutableTable existing = tables.get(new SelectorName(tableName));
288 if (existing == null) {
289 throw new IllegalArgumentException(GraphI18n.tableDoesNotExist.text(tableName));
290 }
291 Set<Column> keyColumns = new HashSet<Column>();
292 for (String columnName : columnNames) {
293 Column existingColumn = existing.getColumnsByName().get(columnName);
294 if (existingColumn == null) {
295 String msg = GraphI18n.schemataKeyReferencesNonExistingColumn.text(tableName, columnName);
296 throw new IllegalArgumentException(msg);
297 }
298 keyColumns.add(existingColumn);
299 }
300 ImmutableTable table = existing.withKey(keyColumns);
301 tables.put(table.getName(), table);
302 return this;
303 }
304
305
306
307
308
309
310
311
312 public Schemata build() {
313
314 for (SelectorName tableName : tablesOrViewsWithExtraColumns) {
315 ImmutableTable table = tables.get(tableName);
316 if (table != null) {
317 tables.put(table.getName(), table.withExtraColumns());
318 }
319 }
320
321 ImmutableSchemata schemata = new ImmutableSchemata(new HashMap<SelectorName, Table>(tables));
322
323
324 Map<SelectorName, QueryCommand> definitions = new HashMap<SelectorName, QueryCommand>(viewDefinitions);
325 boolean added = false;
326 do {
327 added = false;
328 Set<SelectorName> viewNames = new HashSet<SelectorName>(definitions.keySet());
329 for (SelectorName name : viewNames) {
330 QueryCommand command = definitions.get(name);
331
332 PlanHints hints = new PlanHints();
333 hints.validateColumnExistance = false;
334 QueryContext queryContext = new QueryContext(schemata, typeSystem, hints);
335 CanonicalPlanner planner = new CanonicalPlanner();
336 PlanNode plan = planner.createPlan(queryContext, command);
337 if (queryContext.getProblems().hasErrors()) {
338 continue;
339 }
340
341
342 PlanNode project = plan.findAtOrBelow(Type.PROJECT);
343 assert project != null;
344 List<org.modeshape.graph.query.model.Column> columns = project.getPropertyAsList(Property.PROJECT_COLUMNS,
345 org.modeshape.graph.query.model.Column.class);
346 assert !columns.isEmpty();
347
348
349 Map<SelectorName, SelectorName> tableNameByAlias = null;
350 List<Column> viewColumns = new ArrayList<Column>(columns.size());
351 for (org.modeshape.graph.query.model.Column column : columns) {
352
353 Table source = schemata.getTable(column.selectorName());
354 if (source == null) {
355
356 if (tableNameByAlias == null) {
357 tableNameByAlias = Visitors.getSelectorNamesByAlias(command);
358 }
359 SelectorName tableName = tableNameByAlias.get(column.selectorName());
360 if (tableName != null) source = schemata.getTable(tableName);
361 if (source == null) {
362 continue;
363 }
364 }
365 String viewColumnName = column.columnName();
366 String sourceColumnName = column.propertyName();
367 Column sourceColumn = source.getColumn(sourceColumnName);
368 if (sourceColumn == null) {
369 throw new InvalidQueryException(Visitors.readable(command),
370 "The view references a non-existant column '" + column.columnName()
371 + "' in '" + source.getName() + "'");
372 }
373 viewColumns.add(new ImmutableColumn(viewColumnName, sourceColumn.getPropertyType(),
374 sourceColumn.isFullTextSearchable()));
375 }
376 if (viewColumns.size() != columns.size()) {
377
378
379 continue;
380 }
381
382
383 boolean hasExtraColumns = tablesOrViewsWithExtraColumns.contains(name);
384 ImmutableView view = new ImmutableView(name, viewColumns, hasExtraColumns, command);
385 definitions.remove(name);
386 schemata = schemata.with(view);
387 added = true;
388 }
389 } while (added && !definitions.isEmpty());
390
391 if (!definitions.isEmpty()) {
392 QueryCommand command = definitions.values().iterator().next();
393 throw new InvalidQueryException(Visitors.readable(command), "The view definition cannot be resolved: "
394 + Visitors.readable(command));
395 }
396
397 return schemata;
398 }
399 }
400
401 private final Map<SelectorName, Table> tables;
402
403 protected ImmutableSchemata( Map<SelectorName, Table> tables ) {
404 this.tables = Collections.unmodifiableMap(tables);
405 }
406
407
408
409
410
411
412 public Table getTable( SelectorName name ) {
413 return tables.get(name);
414 }
415
416 public ImmutableSchemata with( Table table ) {
417 Map<SelectorName, Table> tables = new HashMap<SelectorName, Table>(this.tables);
418 tables.put(table.getName(), table);
419 return new ImmutableSchemata(tables);
420 }
421
422
423
424
425
426
427 @Override
428 public String toString() {
429 StringBuilder sb = new StringBuilder();
430 boolean first = true;
431 for (Table table : tables.values()) {
432 if (first) first = false;
433 else sb.append('\n');
434 sb.append(table);
435 }
436 return sb.toString();
437 }
438
439 }