2 * Copyright (C) 2012 Openismus GmbH
4 * This file is part of GWT-Glom.
6 * GWT-Glom is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
11 * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with GWT-Glom. If not, see <http://www.gnu.org/licenses/>.
20 package org.glom.web.server;
22 import java.sql.Connection;
23 import java.util.ArrayList;
24 import java.util.List;
26 import org.apache.commons.lang3.StringUtils;
27 import org.glom.libglom.Field;
28 import org.glom.libglom.FieldVector;
29 import org.glom.libglom.LayoutFieldVector;
30 import org.glom.libglom.LayoutItem_Field;
31 import org.glom.libglom.Relationship;
32 import org.glom.libglom.SortClause;
33 import org.glom.libglom.SortFieldPair;
34 import org.glom.libglom.Value;
35 import org.glom.web.shared.libglom.Document;
36 import org.jooq.AggregateFunction;
37 import org.jooq.Condition;
38 import org.jooq.SQLDialect;
39 import org.jooq.SelectFinalStep;
40 import org.jooq.SelectJoinStep;
41 import org.jooq.SelectSelectStep;
42 import org.jooq.conf.RenderKeywordStyle;
43 import org.jooq.conf.RenderNameStyle;
44 import org.jooq.conf.Settings;
45 import org.jooq.impl.Factory;
48 * @author Ben Konrath <ben@bagu.org>
51 public class SqlUtils {
53 public static class UsesRelationship {
55 private Relationship relationship;
56 private Relationship relatedRelationship;
58 public void setRelationship(final Relationship relationship) {
59 this.relationship = relationship;
63 * @param get_related_relationship
65 public void setRelatedRelationship(final Relationship relationship) {
66 this.relatedRelationship = relationship;
69 public Relationship getRelationship() {
73 public Relationship getRelatedRelationship() {
74 return relatedRelationship;
77 private boolean getHasRelationshipName() {
78 if (relationship == null) {
82 if (StringUtils.isEmpty(relationship.get_name())) {
89 private boolean getHasRelatedRelationshipName() {
90 if (relatedRelationship == null) {
94 if (StringUtils.isEmpty(relatedRelationship.get_name())) {
101 public String get_sql_join_alias_name() {
104 if (getHasRelationshipName() && relationship.get_has_fields()) // relationships that link to tables together
107 // We use relationship_name.field_name instead of related_tableName.field_name,
108 // because, in the JOIN below, will specify the relationship_name as an alias for the related table name
109 result += ("relationship_" + relationship.get_name());
111 if (getHasRelatedRelationshipName() && relatedRelationship.get_has_fields()) {
112 result += ('_' + relatedRelationship.get_name());
122 * @see java.lang.Object#hashCode()
125 * @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result +
126 * ((relatedRelationship == null) ? 0 : relatedRelationship.hashCode()); result = prime * result +
127 * ((relationship == null) ? 0 : relationship.hashCode()); return result; }
133 * @see java.lang.Object#equals(java.lang.Object)
136 * TODO: This causes NullPointerExceptions when used from contains().
139 public boolean equals(final Object obj) {
148 if (!(obj instanceof UsesRelationship)) {
152 final UsesRelationship other = (UsesRelationship) obj;
153 if (relationship == null) {
154 if (other.relationship != null) {
157 } else if (!relationship_equals(relationship, other.relationship)) {
161 if (relatedRelationship == null) {
162 if (other.relatedRelationship != null) {
165 } else if (!relationship_equals(relatedRelationship, other.relatedRelationship)) {
173 * We use this utility function because Relationship.equals() fails in the the generated SWIG C++ code with a
174 * NullPointerException.
176 public static boolean relationship_equals(final Relationship a, final Relationship b) {
189 final String a_name = a.get_name();
190 final String b_name = b.get_name();
192 if (!StringUtils.equals(a_name, b_name)) { // TODO: And the rest.
200 // TODO: Change to final ArrayList<LayoutItem_Field> fieldsToGet
201 public static String build_sql_select_with_key(final Connection connection, final String tableName,
202 final LayoutFieldVector fieldsToGet, final Field primaryKey, final Value gdaPrimaryKeyValue) {
204 Condition whereClause = null; // Note that we ignore quickFind.
205 if (gdaPrimaryKeyValue != null) {
206 whereClause = build_simple_where_expression(tableName, primaryKey, gdaPrimaryKeyValue);
209 final SortClause sortClause = null; // Ignored.
210 return build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause, sortClause);
213 public static Condition build_simple_where_expression(final String tableName, final Field primaryKey,
214 final Value gdaPrimaryKeyValue) {
216 Condition result = null;
218 if (primaryKey == null) {
222 final String fieldName = primaryKey.get_name();
223 if (StringUtils.isEmpty(fieldName)) {
227 final org.jooq.Field<Object> field = createField(tableName, fieldName);
228 result = field.equal(gdaPrimaryKeyValue.get_double()); // TODO: Handle other types too.
233 * private static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
234 * final LayoutFieldVector fieldsToGet) { final Condition whereClause = null; return
235 * build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause); }
239 * private static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
240 * final LayoutFieldVector fieldsToGet, final Condition whereClause) { final SortClause sortClause = null; return
241 * build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause, sortClause); }
244 public static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
245 final LayoutFieldVector fieldsToGet, final Condition whereClause, final SortClause sortClause) {
246 final SelectFinalStep step = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
247 whereClause, sortClause);
252 final String query = step.getQuery().getSQL(true);
253 // Log.info("Query: " + query);
257 private static SelectSelectStep createSelect(final Connection connection) {
258 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
259 final Settings settings = factory.getSettings();
260 settings.setRenderNameStyle(RenderNameStyle.QUOTED); // TODO: This doesn't seem to have any effect.
261 settings.setRenderKeywordStyle(RenderKeywordStyle.UPPER); // TODO: Just to make debugging nicer.
263 final SelectSelectStep selectStep = factory.select();
267 private static SelectFinalStep build_sql_select_step_with_where_clause(final Connection connection,
268 final String tableName, final LayoutFieldVector fieldsToGet, final Condition whereClause,
269 final SortClause sortClause) {
271 final SelectSelectStep selectStep = createSelect(connection);
273 // Add the fields, and any necessary joins:
274 final List<UsesRelationship> listRelationships = build_sql_select_add_fields_to_get(selectStep, tableName,
275 fieldsToGet, sortClause, false /* extraJoin */);
277 final SelectJoinStep joinStep = selectStep.from(tableName);
279 // LEFT OUTER JOIN will get the field values from the other tables,
280 // and give us our fields for this table even if there is no corresponding value in the other table.
281 for (final UsesRelationship usesRelationship : listRelationships) {
282 builder_add_join(joinStep, usesRelationship);
285 SelectFinalStep finalStep = joinStep;
286 if (whereClause != null) {
287 finalStep = joinStep.where(whereClause);
293 public static String build_sql_count_select_with_where_clause(final Connection connection, final String tableName,
294 final LayoutFieldVector fieldsToGet) {
295 final SelectFinalStep selectInner = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
297 return build_sql_select_count_rows(connection, selectInner);
300 public static String build_sql_count_select_with_where_clause(final Connection connection, final String tableName,
301 final LayoutFieldVector fieldsToGet, final Condition whereClause) {
302 final SelectFinalStep selectInner = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
304 return build_sql_select_count_rows(connection, selectInner);
307 private static String build_sql_select_count_rows(final Connection connection, final SelectFinalStep selectInner) {
308 // TODO: Find a way to do this with the jOOQ API:
309 final SelectSelectStep select = createSelect(connection);
311 final org.jooq.Field<?> field = Factory.field("*");
312 final AggregateFunction<?> count = Factory.count(field);
313 select.select(count).from(selectInner);
314 return select.getQuery().getSQL(true);
315 // return "SELECT COUNT(*) FROM (" + query + ") AS glomarbitraryalias";
318 private static List<UsesRelationship> build_sql_select_add_fields_to_get(SelectSelectStep step,
319 final String tableName, final LayoutFieldVector fieldsToGet, final SortClause sortClause,
320 final boolean extraJoin) {
322 // Get all relationships used in the query:
323 final List<UsesRelationship> listRelationships = new ArrayList<UsesRelationship>();
325 final int layoutFieldsSize = Utils.safeLongToInt(fieldsToGet.size());
326 for (int i = 0; i < layoutFieldsSize; i++) {
327 final LayoutItem_Field layout_item = fieldsToGet.get(i);
328 add_to_relationships_list(listRelationships, layout_item);
331 if (sortClause != null) {
332 final int sortFieldsSize = Utils.safeLongToInt(sortClause.size());
333 for (int i = 0; i < sortFieldsSize; i++) {
334 final SortFieldPair pair = sortClause.get(i);
335 final LayoutItem_Field layout_item = pair.getFirst();
336 add_to_relationships_list(listRelationships, layout_item);
340 boolean one_added = false;
341 for (int i = 0; i < layoutFieldsSize; i++) {
342 final LayoutItem_Field layout_item = fieldsToGet.get(i);
344 if (layout_item == null) {
345 // g_warn_if_reached();
349 // Get the parent, such as the table name, or the alias name for the join:
350 // final String parent = layout_item.get_sql_table_or_join_alias_name(tableName);
353 * TODO: const LayoutItem_FieldSummary* fieldsummary = dynamic_cast<const
354 * LayoutItem_FieldSummary*>(layout_item.obj()); if(fieldsummary) { const Gnome::Gda::SqlBuilder::Id
355 * id_function = builder->add_function( fieldsummary->get_summary_type_sql(),
356 * builder->add_field_id(layout_item->get_name(), tableName)); builder->add_field_value_id(id_function); }
359 final org.jooq.Field<?> field = createField(tableName, layout_item);
361 step = step.select(field);
363 // Avoid duplicate records with doubly-related fields:
364 // TODO: if(extra_join)
365 // builder->select_group_by(id);
373 // TODO: std::cerr << G_STRFUNC << ": No fields added: fieldsToGet.size()=" << fieldsToGet.size() <<
375 return listRelationships;
378 return listRelationships;
381 private static org.jooq.Field<Object> createField(final String tableName, final String fieldName) {
382 final String sql_field_name = get_sql_field_name(tableName, fieldName);
383 if (StringUtils.isEmpty(sql_field_name)) {
387 return Factory.field(sql_field_name);
390 private static org.jooq.Field<Object> createField(final String tableName, final LayoutItem_Field layoutField) {
391 final String sql_field_name = get_sql_field_name(tableName, layoutField);
392 if (StringUtils.isEmpty(sql_field_name)) {
396 return Factory.field(sql_field_name);
399 private static String get_sql_field_name(final String tableName, final String fieldName) {
401 if (StringUtils.isEmpty(tableName)) {
405 if (StringUtils.isEmpty(fieldName)) {
409 // TODO: Quoting, escaping, etc:
410 return tableName + "." + fieldName;
413 private static String get_sql_field_name(final String tableName, final LayoutItem_Field layoutItemField) {
415 if (layoutItemField == null) {
419 if (StringUtils.isEmpty(tableName)) {
423 // TODO: Quoting, escaping, etc:
424 return get_sql_field_name(layoutItemField.get_sql_table_or_join_alias_name(tableName),
425 layoutItemField.get_name());
428 private static void add_to_relationships_list(final List<UsesRelationship> listRelationships,
429 final LayoutItem_Field layout_item) {
431 if (layout_item == null) {
435 if (!layout_item.get_has_relationship_name()) {
439 // If this is a related relationship, add the first-level relationship too, so that the related relationship can
440 // be defined in terms of it:
441 // TODO: //If the table is not yet in the list:
442 if (layout_item.get_has_related_relationship_name()) {
443 final UsesRelationship usesRel = new UsesRelationship();
444 usesRel.setRelationship(layout_item.get_relationship());
446 // Remove any UsesRelationship that has only the same relationship (not related relationship),
447 // to avoid adding that part of the relationship to the SQL twice (two identical JOINS).
448 // listRemoveIfUsesRelationship(listRelationships, usesRel.getRelationship());
450 if (!listRelationships.contains(usesRel)) {
451 // These need to be at the front, so that related relationships can use
452 // them later in the SQL statement.
453 listRelationships.add(usesRel);
458 // Add the relationship to the list:
459 final UsesRelationship usesRel = new UsesRelationship();
460 usesRel.setRelationship(layout_item.get_relationship());
461 usesRel.setRelatedRelationship(layout_item.get_related_relationship());
462 if (!listRelationships.contains(usesRel)) {
463 listRelationships.add(usesRel);
469 * @param listRelationships
470 * @param relationship
473 * private static void listRemoveIfUsesRelationship(final List<UsesRelationship> listRelationships, final
474 * Relationship relationship) { if (relationship == null) { return; }
476 * final Iterator<UsesRelationship> i = listRelationships.iterator(); while (i.hasNext()) { final UsesRelationship
477 * eachUsesRel = i.next(); if (eachUsesRel == null) continue;
479 * // Ignore these: if (eachUsesRel.getHasRelatedRelationshipName()) { continue; }
481 * final Relationship eachRel = eachUsesRel.getRelationship(); if (eachRel == null) { continue; }
483 * Log.info("Checking: rel name=" + relationship.get_name() + ", eachRel name=" + eachRel.get_name());
485 * if (UsesRelationship.relationship_equals(relationship, eachRel)) { i.remove(); Log.info(" Removed"); } else {
486 * Log.info(" not equal"); }
491 private static void builder_add_join(SelectJoinStep step, final UsesRelationship uses_relationship) {
492 final Relationship relationship = uses_relationship.getRelationship();
493 if (!relationship.get_has_fields()) { // TODO: Handle related_record has_fields.
494 if (relationship.get_has_to_table()) {
495 // It is a relationship that only specifies the table, without specifying linking fields:
497 // TODO: from() takes SQL, not specifically a table name, so this is unsafe.
498 // TODO: stepResult = step.from(relationship.get_to_table());
504 // Define the alias name as returned by get_sql_join_alias_name():
506 // Specify an alias, to avoid ambiguity when using 2 relationships to the same table.
507 final String alias_name = uses_relationship.get_sql_join_alias_name();
510 if (!uses_relationship.getHasRelatedRelationshipName()) {
512 final org.jooq.Field<Object> fieldFrom = createField(relationship.get_from_table(),
513 relationship.get_from_field());
514 final org.jooq.Field<Object> fieldTo = createField(alias_name, relationship.get_to_field());
515 final Condition condition = fieldFrom.equal(fieldTo);
517 // TODO: join() takes SQL, not specifically an alias name, so this is unsafe.
518 // Note that LEFT JOIN (used in libglom/GdaSqlBuilder) is apparently the same as LEFT OUTER JOIN.
519 step = step.leftOuterJoin(relationship.get_to_table() + " AS " + alias_name).on(condition);
521 final UsesRelationship parent_relationship = new UsesRelationship();
522 parent_relationship.setRelationship(relationship);
523 final Relationship relatedRelationship = uses_relationship.getRelatedRelationship();
525 final org.jooq.Field<Object> fieldFrom = createField(parent_relationship.get_sql_join_alias_name(),
526 relatedRelationship.get_from_field());
527 final org.jooq.Field<Object> fieldTo = createField(alias_name, relatedRelationship.get_to_field());
528 final Condition condition = fieldFrom.equal(fieldTo);
530 // TODO: join() takes SQL, not specifically an alias name, so this is unsafe.
531 // Note that LEFT JOIN (used in libglom/GdaSqlBuilder) is apparently the same as LEFT OUTER JOIN.
532 step = step.leftOuterJoin(relatedRelationship.get_to_table() + " AS " + alias_name).on(condition);
536 public static Condition get_find_where_clause_quick(final Document document, final String tableName,
537 final Value quickFindValue) {
538 if (StringUtils.isEmpty(tableName)) {
542 // TODO: if(Conversions::value_is_empty(quick_search))
543 // return Gnome::Gda::SqlExpr();
545 Condition condition = null;
547 // TODO: Cache the list of all fields, as well as caching (m_Fields) the list of all visible fields:
548 final FieldVector fields = document.get_table_fields(tableName);
550 final int fieldsSize = Utils.safeLongToInt(fields.size());
551 for (int i = 0; i < fieldsSize; i++) {
552 final Field field = fields.get(i);
557 if (field.get_glom_type() != Field.glom_field_type.TYPE_TEXT) {
561 final org.jooq.Field<Object> jooqField = createField(tableName, field.get_name());
562 final Condition thisCondition = jooqField.equal(quickFindValue.get_string());
564 if (condition == null) {
565 condition = thisCondition;
567 condition = condition.or(thisCondition);