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.LayoutFieldVector;
28 import org.glom.libglom.LayoutItem_Field;
29 import org.glom.libglom.Relationship;
30 import org.glom.libglom.SortClause;
31 import org.glom.libglom.SortFieldPair;
32 import org.glom.libglom.Value;
33 import org.glom.web.shared.libglom.Document;
34 import org.glom.web.shared.libglom.Field;
35 import org.jooq.AggregateFunction;
36 import org.jooq.Condition;
37 import org.jooq.SQLDialect;
38 import org.jooq.SelectFinalStep;
39 import org.jooq.SelectJoinStep;
40 import org.jooq.SelectSelectStep;
41 import org.jooq.conf.RenderKeywordStyle;
42 import org.jooq.conf.RenderNameStyle;
43 import org.jooq.conf.Settings;
44 import org.jooq.impl.Factory;
47 * @author Ben Konrath <ben@bagu.org>
50 public class SqlUtils {
52 public static class UsesRelationship {
54 private Relationship relationship;
55 private Relationship relatedRelationship;
57 public void setRelationship(final Relationship relationship) {
58 this.relationship = relationship;
62 * @param get_related_relationship
64 public void setRelatedRelationship(final Relationship relationship) {
65 this.relatedRelationship = relationship;
68 public Relationship getRelationship() {
72 public Relationship getRelatedRelationship() {
73 return relatedRelationship;
76 private boolean getHasRelationshipName() {
77 if (relationship == null) {
81 if (StringUtils.isEmpty(relationship.get_name())) {
88 private boolean getHasRelatedRelationshipName() {
89 if (relatedRelationship == null) {
93 if (StringUtils.isEmpty(relatedRelationship.get_name())) {
100 public String get_sql_join_alias_name() {
103 if (getHasRelationshipName() && relationship.get_has_fields()) // relationships that link to tables together
106 // We use relationship_name.field_name instead of related_tableName.field_name,
107 // because, in the JOIN below, will specify the relationship_name as an alias for the related table name
108 result += ("relationship_" + relationship.get_name());
110 if (getHasRelatedRelationshipName() && relatedRelationship.get_has_fields()) {
111 result += ('_' + relatedRelationship.get_name());
121 * @see java.lang.Object#hashCode()
124 * @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result +
125 * ((relatedRelationship == null) ? 0 : relatedRelationship.hashCode()); result = prime * result +
126 * ((relationship == null) ? 0 : relationship.hashCode()); return result; }
132 * @see java.lang.Object#equals(java.lang.Object)
135 * TODO: This causes NullPointerExceptions when used from contains().
138 public boolean equals(final Object obj) {
147 if (!(obj instanceof UsesRelationship)) {
151 final UsesRelationship other = (UsesRelationship) obj;
152 if (relationship == null) {
153 if (other.relationship != null) {
156 } else if (!relationship_equals(relationship, other.relationship)) {
160 if (relatedRelationship == null) {
161 if (other.relatedRelationship != null) {
164 } else if (!relationship_equals(relatedRelationship, other.relatedRelationship)) {
172 * We use this utility function because Relationship.equals() fails in the the generated SWIG C++ code with a
173 * NullPointerException.
175 public static boolean relationship_equals(final Relationship a, final Relationship b) {
188 final String a_name = a.get_name();
189 final String b_name = b.get_name();
191 if (!StringUtils.equals(a_name, b_name)) { // TODO: And the rest.
199 // TODO: Change to final ArrayList<LayoutItem_Field> fieldsToGet
200 public static String build_sql_select_with_key(final Connection connection, final String tableName,
201 final LayoutFieldVector fieldsToGet, final Field primaryKey, final Value gdaPrimaryKeyValue) {
203 Condition whereClause = null; // Note that we ignore quickFind.
204 if (gdaPrimaryKeyValue != null) {
205 whereClause = build_simple_where_expression(tableName, primaryKey, gdaPrimaryKeyValue);
208 final SortClause sortClause = null; // Ignored.
209 return build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause, sortClause);
212 public static Condition build_simple_where_expression(final String tableName, final Field primaryKey,
213 final Value gdaPrimaryKeyValue) {
215 Condition result = null;
217 if (primaryKey == null) {
221 final String fieldName = primaryKey.get_name();
222 if (StringUtils.isEmpty(fieldName)) {
226 final org.jooq.Field<Object> field = createField(tableName, fieldName);
227 result = field.equal(gdaPrimaryKeyValue.get_double()); // TODO: Handle other types too.
232 * private static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
233 * final LayoutFieldVector fieldsToGet) { final Condition whereClause = null; return
234 * build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause); }
238 * private static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
239 * final LayoutFieldVector fieldsToGet, final Condition whereClause) { final SortClause sortClause = null; return
240 * build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause, sortClause); }
243 public static String build_sql_select_with_where_clause(final Connection connection, final String tableName,
244 final LayoutFieldVector fieldsToGet, final Condition whereClause, final SortClause sortClause) {
245 final SelectFinalStep step = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
246 whereClause, sortClause);
251 final String query = step.getQuery().getSQL(true);
252 // Log.info("Query: " + query);
256 private static SelectSelectStep createSelect(final Connection connection) {
257 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
258 final Settings settings = factory.getSettings();
259 settings.setRenderNameStyle(RenderNameStyle.QUOTED); // TODO: This doesn't seem to have any effect.
260 settings.setRenderKeywordStyle(RenderKeywordStyle.UPPER); // TODO: Just to make debugging nicer.
262 final SelectSelectStep selectStep = factory.select();
266 private static SelectFinalStep build_sql_select_step_with_where_clause(final Connection connection,
267 final String tableName, final LayoutFieldVector fieldsToGet, final Condition whereClause,
268 final SortClause sortClause) {
270 final SelectSelectStep selectStep = createSelect(connection);
272 // Add the fields, and any necessary joins:
273 final List<UsesRelationship> listRelationships = build_sql_select_add_fields_to_get(selectStep, tableName,
274 fieldsToGet, sortClause, false /* extraJoin */);
276 final SelectJoinStep joinStep = selectStep.from(tableName);
278 // LEFT OUTER JOIN will get the field values from the other tables,
279 // and give us our fields for this table even if there is no corresponding value in the other table.
280 for (final UsesRelationship usesRelationship : listRelationships) {
281 builder_add_join(joinStep, usesRelationship);
284 SelectFinalStep finalStep = joinStep;
285 if (whereClause != null) {
286 finalStep = joinStep.where(whereClause);
292 public static String build_sql_count_select_with_where_clause(final Connection connection, final String tableName,
293 final LayoutFieldVector fieldsToGet) {
294 final SelectFinalStep selectInner = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
296 return build_sql_select_count_rows(connection, selectInner);
299 public static String build_sql_count_select_with_where_clause(final Connection connection, final String tableName,
300 final LayoutFieldVector fieldsToGet, final Condition whereClause) {
301 final SelectFinalStep selectInner = build_sql_select_step_with_where_clause(connection, tableName, fieldsToGet,
303 return build_sql_select_count_rows(connection, selectInner);
306 private static String build_sql_select_count_rows(final Connection connection, final SelectFinalStep selectInner) {
307 // TODO: Find a way to do this with the jOOQ API:
308 final SelectSelectStep select = createSelect(connection);
310 final org.jooq.Field<?> field = Factory.field("*");
311 final AggregateFunction<?> count = Factory.count(field);
312 select.select(count).from(selectInner);
313 return select.getQuery().getSQL(true);
314 // return "SELECT COUNT(*) FROM (" + query + ") AS glomarbitraryalias";
317 private static List<UsesRelationship> build_sql_select_add_fields_to_get(SelectSelectStep step,
318 final String tableName, final LayoutFieldVector fieldsToGet, final SortClause sortClause,
319 final boolean extraJoin) {
321 // Get all relationships used in the query:
322 final List<UsesRelationship> listRelationships = new ArrayList<UsesRelationship>();
324 final int layoutFieldsSize = Utils.safeLongToInt(fieldsToGet.size());
325 for (int i = 0; i < layoutFieldsSize; i++) {
326 final LayoutItem_Field layout_item = fieldsToGet.get(i);
327 add_to_relationships_list(listRelationships, layout_item);
330 if (sortClause != null) {
331 final int sortFieldsSize = Utils.safeLongToInt(sortClause.size());
332 for (int i = 0; i < sortFieldsSize; i++) {
333 final SortFieldPair pair = sortClause.get(i);
334 final LayoutItem_Field layout_item = pair.getFirst();
335 add_to_relationships_list(listRelationships, layout_item);
339 boolean one_added = false;
340 for (int i = 0; i < layoutFieldsSize; i++) {
341 final LayoutItem_Field layout_item = fieldsToGet.get(i);
343 if (layout_item == null) {
344 // g_warn_if_reached();
348 // Get the parent, such as the table name, or the alias name for the join:
349 // final String parent = layout_item.get_sql_table_or_join_alias_name(tableName);
352 * TODO: const LayoutItem_FieldSummary* fieldsummary = dynamic_cast<const
353 * LayoutItem_FieldSummary*>(layout_item.obj()); if(fieldsummary) { const Gnome::Gda::SqlBuilder::Id
354 * id_function = builder->add_function( fieldsummary->get_summary_type_sql(),
355 * builder->add_field_id(layout_item->get_name(), tableName)); builder->add_field_value_id(id_function); }
358 final org.jooq.Field<?> field = createField(tableName, layout_item);
360 step = step.select(field);
362 // Avoid duplicate records with doubly-related fields:
363 // TODO: if(extra_join)
364 // builder->select_group_by(id);
372 // TODO: std::cerr << G_STRFUNC << ": No fields added: fieldsToGet.size()=" << fieldsToGet.size() <<
374 return listRelationships;
377 return listRelationships;
380 private static org.jooq.Field<Object> createField(final String tableName, final String fieldName) {
381 final String sql_field_name = get_sql_field_name(tableName, fieldName);
382 if (StringUtils.isEmpty(sql_field_name)) {
386 return Factory.field(sql_field_name);
389 private static org.jooq.Field<Object> createField(final String tableName, final LayoutItem_Field layoutField) {
390 final String sql_field_name = get_sql_field_name(tableName, layoutField);
391 if (StringUtils.isEmpty(sql_field_name)) {
395 return Factory.field(sql_field_name);
398 private static String get_sql_field_name(final String tableName, final String fieldName) {
400 if (StringUtils.isEmpty(tableName)) {
404 if (StringUtils.isEmpty(fieldName)) {
408 // TODO: Quoting, escaping, etc:
409 return tableName + "." + fieldName;
412 private static String get_sql_field_name(final String tableName, final LayoutItem_Field layoutItemField) {
414 if (layoutItemField == null) {
418 if (StringUtils.isEmpty(tableName)) {
422 // TODO: Quoting, escaping, etc:
423 return get_sql_field_name(layoutItemField.get_sql_table_or_join_alias_name(tableName),
424 layoutItemField.get_name());
427 private static void add_to_relationships_list(final List<UsesRelationship> listRelationships,
428 final LayoutItem_Field layout_item) {
430 if (layout_item == null) {
434 if (!layout_item.get_has_relationship_name()) {
438 // If this is a related relationship, add the first-level relationship too, so that the related relationship can
439 // be defined in terms of it:
440 // TODO: //If the table is not yet in the list:
441 if (layout_item.get_has_related_relationship_name()) {
442 final UsesRelationship usesRel = new UsesRelationship();
443 usesRel.setRelationship(layout_item.get_relationship());
445 // Remove any UsesRelationship that has only the same relationship (not related relationship),
446 // to avoid adding that part of the relationship to the SQL twice (two identical JOINS).
447 // listRemoveIfUsesRelationship(listRelationships, usesRel.getRelationship());
449 if (!listRelationships.contains(usesRel)) {
450 // These need to be at the front, so that related relationships can use
451 // them later in the SQL statement.
452 listRelationships.add(usesRel);
457 // Add the relationship to the list:
458 final UsesRelationship usesRel = new UsesRelationship();
459 usesRel.setRelationship(layout_item.get_relationship());
460 usesRel.setRelatedRelationship(layout_item.get_related_relationship());
461 if (!listRelationships.contains(usesRel)) {
462 listRelationships.add(usesRel);
468 * @param listRelationships
469 * @param relationship
472 * private static void listRemoveIfUsesRelationship(final List<UsesRelationship> listRelationships, final
473 * Relationship relationship) { if (relationship == null) { return; }
475 * final Iterator<UsesRelationship> i = listRelationships.iterator(); while (i.hasNext()) { final UsesRelationship
476 * eachUsesRel = i.next(); if (eachUsesRel == null) continue;
478 * // Ignore these: if (eachUsesRel.getHasRelatedRelationshipName()) { continue; }
480 * final Relationship eachRel = eachUsesRel.getRelationship(); if (eachRel == null) { continue; }
482 * Log.info("Checking: rel name=" + relationship.get_name() + ", eachRel name=" + eachRel.get_name());
484 * if (UsesRelationship.relationship_equals(relationship, eachRel)) { i.remove(); Log.info(" Removed"); } else {
485 * Log.info(" not equal"); }
490 private static void builder_add_join(SelectJoinStep step, final UsesRelationship uses_relationship) {
491 final Relationship relationship = uses_relationship.getRelationship();
492 if (!relationship.get_has_fields()) { // TODO: Handle related_record has_fields.
493 if (relationship.get_has_to_table()) {
494 // It is a relationship that only specifies the table, without specifying linking fields:
496 // TODO: from() takes SQL, not specifically a table name, so this is unsafe.
497 // TODO: stepResult = step.from(relationship.get_to_table());
503 // Define the alias name as returned by get_sql_join_alias_name():
505 // Specify an alias, to avoid ambiguity when using 2 relationships to the same table.
506 final String alias_name = uses_relationship.get_sql_join_alias_name();
509 if (!uses_relationship.getHasRelatedRelationshipName()) {
511 final org.jooq.Field<Object> fieldFrom = createField(relationship.get_from_table(),
512 relationship.get_from_field());
513 final org.jooq.Field<Object> fieldTo = createField(alias_name, relationship.get_to_field());
514 final Condition condition = fieldFrom.equal(fieldTo);
516 // TODO: join() takes SQL, not specifically an alias name, so this is unsafe.
517 // Note that LEFT JOIN (used in libglom/GdaSqlBuilder) is apparently the same as LEFT OUTER JOIN.
518 step = step.leftOuterJoin(relationship.get_to_table() + " AS " + alias_name).on(condition);
520 final UsesRelationship parent_relationship = new UsesRelationship();
521 parent_relationship.setRelationship(relationship);
522 final Relationship relatedRelationship = uses_relationship.getRelatedRelationship();
524 final org.jooq.Field<Object> fieldFrom = createField(parent_relationship.get_sql_join_alias_name(),
525 relatedRelationship.get_from_field());
526 final org.jooq.Field<Object> fieldTo = createField(alias_name, relatedRelationship.get_to_field());
527 final Condition condition = fieldFrom.equal(fieldTo);
529 // TODO: join() takes SQL, not specifically an alias name, so this is unsafe.
530 // Note that LEFT JOIN (used in libglom/GdaSqlBuilder) is apparently the same as LEFT OUTER JOIN.
531 step = step.leftOuterJoin(relatedRelationship.get_to_table() + " AS " + alias_name).on(condition);
535 public static Condition get_find_where_clause_quick(final Document document, final String tableName,
536 final Value quickFindValue) {
537 if (StringUtils.isEmpty(tableName)) {
541 // TODO: if(Conversions::value_is_empty(quick_search))
542 // return Gnome::Gda::SqlExpr();
544 Condition condition = null;
546 // TODO: Cache the list of all fields, as well as caching (m_Fields) the list of all visible fields:
547 final List<Field> fields = document.get_table_fields(tableName);
549 final int fieldsSize = Utils.safeLongToInt(fields.size());
550 for (int i = 0; i < fieldsSize; i++) {
551 final Field field = fields.get(i);
556 if (field.get_glom_type() != Field.glom_field_type.TYPE_TEXT) {
560 final org.jooq.Field<Object> jooqField = createField(tableName, field.get_name());
561 final Condition thisCondition = jooqField.equal(quickFindValue.get_string());
563 if (condition == null) {
564 condition = thisCondition;
566 condition = condition.or(thisCondition);