/** * midgard_query_builder_new(): * @mgd: #MidgardConnection instance * @classname: any #MidgardDBObjectClass derived class' name * * Returns: new #MidgardQueryBuilder instance or %NULL if target class is not registered in GType system * or it's not #MidgardDBObjectClass class derived one. */ MidgardQueryBuilder *midgard_query_builder_new( MidgardConnection *mgd, const gchar *classname) { g_assert(mgd != NULL); g_assert(classname != NULL); GType class_type = g_type_from_name(classname); if(!__type_is_valid(class_type)) { g_warning("Can not initialize Midgard Query Builder for '%s'. It's not registered GType system class", classname); MIDGARD_ERRNO_SET(mgd, MGD_ERR_INVALID_OBJECT); return NULL; } MidgardQueryBuilder *builder = g_object_new(MIDGARD_TYPE_QUERY_BUILDER, NULL); builder->priv = midgard_query_builder_private_new(); builder->priv->mgd = mgd; builder->priv->type = g_type_from_name(classname); builder->priv->include_deleted = FALSE; builder->priv->error = 0; MidgardDBObjectClass *klass = (MidgardDBObjectClass*) g_type_class_peek(class_type); if (klass->dbpriv == NULL) { g_warning("Given %s class has no storage definitions", g_type_name(class_type)); g_object_unref(builder); return NULL; } builder->priv->schema = klass->dbpriv->storage_data; gchar **tables = g_strsplit(midgard_core_class_get_tables(MIDGARD_DBOBJECT_CLASS(klass)), ",", 0); guint i = 0; while(tables[i] != NULL) { midgard_core_qb_add_table(builder, (const gchar *)g_strchug(tables[i])); i++; } g_strfreev(tables); builder->priv->offset = 0; builder->priv->limit = G_MAXUINT; if (builder->priv->type && builder->priv->schema) { return builder; } else { g_object_unref(builder); return NULL; } }
/** * midgard_query_builder_add_constraint_with_property: * @builder: #MidgardQueryBuilder instance * @property_a: property name * @op: comparison operator * @property_b: property name * * Adds named property constraint to the given query builder. * Unlike add_constraint method, this one accepts property name * instead of scalar value. The difference is that with add_constraint * method you can compare property with particular value, while using * add_constraint_with_property method you can compare two different * properties without any need to know their values. * For example, you should use this method if you want to select only * those objects which has been revised after publication time, and particular * date doesn't matter. * * <example> * <programlisting> * * midgard_query_builder_add_constraint_with_property(builder, "metadata.revised", ">", "metadata.published"); * * </programlisting> * </example> * * @See: midgard_query_builder_add_constraint() * * Returns: %TRUE if properties' names are valid, %FALSE otherwise */ gboolean midgard_query_builder_add_constraint_with_property( MidgardQueryBuilder *builder, const gchar *property_a, const gchar *op, const gchar *property_b) { g_assert(builder != NULL); g_assert(property_a != NULL); g_assert(op != NULL); g_assert(property_b != NULL); MidgardDBObjectClass *klass = MIDGARD_DBOBJECT_CLASS(g_type_class_peek(builder->priv->type)); MidgardCoreQueryConstraint *constraint = midgard_core_query_constraint_new(); midgard_core_query_constraint_set_builder(constraint, builder); midgard_core_query_constraint_set_class(constraint, klass); if(!midgard_core_query_constraint_parse_property(&constraint, klass, property_a)) return FALSE; constraint->priv->current = constraint->priv->prop_right; if(!midgard_core_query_constraint_parse_property(&constraint, klass, property_b)) return FALSE; constraint->priv->condition_operator = g_strdup(op); midgard_core_query_constraint_build_condition(constraint); midgard_core_qb_add_table(builder, constraint->priv->prop_left->table); midgard_core_qb_add_table(builder, constraint->priv->prop_right->table); if(builder->priv->grouping_ref > 0) { MidgardGroupConstraint *group = builder->priv->group_constraint; midgard_group_constraint_add_constraint(group, constraint); return TRUE; } else { midgard_core_qb_add_constraint(builder, constraint); } return TRUE; }
MidgardQueryOrder *midgard_core_query_order_new( MidgardQueryBuilder *builder, const gchar *name, const gchar *dir) { g_assert(builder != NULL); g_assert(name != NULL); const gchar *user_dir = dir; if(user_dir == NULL) user_dir = "ASC"; guint i = 0; while(valid_dirs[i] != NULL) { if (g_str_equal(user_dir, valid_dirs[i])) break; i++; } if(valid_dirs[i] == NULL) { g_warning("Invalid order direction: %s", dir); return NULL; } MidgardQueryOrder *order = g_new(MidgardQueryOrder, 1); order->constraint = midgard_core_query_constraint_new(); order->constraint->priv->order_dir = g_strdup(dir); /* Explicitly set builder. It's required for proper constraint's property "parser". * Without builder, implicit joins (for example) are not added */ midgard_core_query_constraint_set_builder(order->constraint, builder); MidgardDBObjectClass *klass = MIDGARD_DBOBJECT_CLASS(g_type_class_peek(builder->priv->type)); if(!midgard_core_query_constraint_parse_property(&(order->constraint), klass, name)) { midgard_core_query_order_free(order); return FALSE; } midgard_core_qb_add_table(builder, order->constraint->priv->prop_left->table); return order; }
/** * midgard_query_builder_add_constraint(): * @builder: #MidgardQueryBuilder instance * @name: property name used for this constraint * @op: comparison operator * @value: value used in comparison * * Adds a constraint to the given query builder. The constraint is * expressed as a triple of a field name, a comparison operator, and * a comparison value. * * <para> * @name referes to a property of the queried Midgard object class. * For example: #MidgardQueryBuilder has been initialized for person * class which has lastname property registered. * <example> * <programlisting> * * GValue value = {0, }; * g_value_init(&value, G_TYPE_STRING); * g_value_set_string(&value, "smith"); * * midgard_query_builder_add_constraint(builder, "lastname", "=", &value); * * </programlisting> * </example> * </para> * <para> * It also can be name of the linked class property or reserved one. * A dot '.' is used to separate properties for special constraints. * If such special constraint property is used, #MidgardQueryBuilder * performs right join. * <itemizedlist> * <listitem><para> * First property is the one registered for given class which is a link * to property of different class. Second is a property of target class. * For example: person object has property 'friend' which holds some identifier * (id or guid) to friend class property, and friend class has property 'nick'. * In such case we can use constraint and comparison using friend property, * even if #MidgardQueryBuilder has been initialized for person class. * <example> * <programlisting> * * GValue value = {0, }; * g_value_init(&value, G_TYPE_STRING); * g_value_set_string(&value, "Lancelot"); * * midgard_query_builder_add_constraint(builder, "friend.nick", "=", &value); * * </programlisting> * </example> * </para></listitem> * <listitem><para> * There are three reserved words which have special meaning for query builder. * 'metadata', 'parameter' and 'attachment'. If one of them is used, query builder * will make (if necessary) right join and query objects against dependent class table. * <example> * <programlisting> * * GValue value = {0, }; * g_value_init(&value, G_TYPE_STRING); * g_value_set_string(&value, "avatar"); * * midgard_query_builder_add_constraint(builder, "attachment.name", "=", &value); * * </programlisting> * </example> * </para></listitem> * </itemizedlist> * </para> * * <para> * The comparison operator is a string representation of the requested comparison. * Available operators are =, <>, <, >, <=, >=, LIKE, NOT LIKE, IN, INTREE. * </para> * * <para> * The given @value is copied and converted into the property type before comparison. * </para> * * Returns: %TRUE if constraint is valid, %FALSE otherwise */ gboolean midgard_query_builder_add_constraint( MidgardQueryBuilder *builder, const gchar *name, const gchar *op, const GValue *value) { g_assert(builder); g_assert(name); g_assert(op); g_assert(value); MidgardCoreQueryConstraint *constraint = midgard_core_query_constraint_new(); midgard_core_query_constraint_set_builder(constraint, builder); midgard_core_query_constraint_set_class(constraint, MIDGARD_DBOBJECT_CLASS(g_type_class_peek(builder->priv->type))); if(!midgard_core_query_constraint_add_operator(constraint, op)) return FALSE; if(!midgard_core_query_constraint_parse_property(&constraint, MIDGARD_DBOBJECT_CLASS(g_type_class_peek(builder->priv->type)), name)) return FALSE; if(!midgard_core_query_constraint_add_value(constraint, value)) return FALSE; midgard_core_query_constraint_build_condition(constraint); /* FIXME, table should be stored per every constraint, order, whatever */ midgard_core_qb_add_table(builder, constraint->priv->prop_left->table); if(builder->priv->grouping_ref > 0) { MidgardGroupConstraint *group = (MidgardGroupConstraint *)builder->priv->group_constraint; midgard_group_constraint_add_constraint(group, constraint); return TRUE; } midgard_core_qb_add_constraint(builder, constraint); return TRUE; }
gboolean midgard_core_query_constraint_parse_property (MidgardCoreQueryConstraint **constraint, MidgardDBObjectClass *klass, const gchar *name) { g_assert(name != NULL); if (!klass) klass = MIDGARD_DBOBJECT_CLASS((*constraint)->priv->klass); (*constraint)->priv->current->table = midgard_core_class_get_table(MIDGARD_DBOBJECT_CLASS(klass)); /* This is optional and might be set to NULL explicitly. We need to parse property for any class which needs this */ MidgardQueryBuilder *builder = (*constraint)->priv->builder; gchar **spltd = g_strsplit(name, ".", 0); guint i = 0; /* We can support max 3 tokens */ while(spltd[i] != NULL) i++; if(i == 1) { g_strfreev(spltd); /* FIXME , it should be done by some more generic function */ GParamSpec *pspec = g_object_class_find_property(G_OBJECT_CLASS(klass), name); if(!pspec) { g_warning("%s is not a member of %s", name, G_OBJECT_CLASS_NAME(klass)); return FALSE; } const gchar *stable = midgard_core_class_get_property_table( MIDGARD_DBOBJECT_CLASS(klass), name); (*constraint)->priv->pspec = pspec; (*constraint)->priv->current->field = midgard_core_class_get_property_colname( MIDGARD_DBOBJECT_CLASS(klass), name); (*constraint)->priv->current->table = stable; if (builder) midgard_core_qb_add_table(builder, stable); (*constraint)->priv->klass = G_OBJECT_CLASS(klass); (*constraint)->priv->propname = name; if(g_str_equal(name, "guid")) (*constraint)->priv->current->field = "guid"; return TRUE; } if(i > 3) { g_warning("Failed to parse '%s'. At most 3 tokens allowed", name); g_strfreev(spltd); return FALSE; } gboolean parsed = FALSE; gboolean do_link = TRUE; guint j = 0; while(spltd[j] != NULL) { if(j+1 == i) do_link = FALSE; parsed = __set_schema_property_attr (builder, &klass, constraint, (const gchar *) spltd[j], do_link); if(!parsed) { g_strfreev(spltd); return FALSE; } j++; } g_strfreev(spltd); return TRUE; }
static gboolean __set_schema_property_attr( MidgardQueryBuilder *builder, MidgardDBObjectClass **klass, MidgardCoreQueryConstraint **constraint, const gchar *name, gboolean do_link) { MgdSchemaPropertyAttr *attr = NULL; const gchar *target_property = NULL; const gchar *link_property = NULL; /* Set klass and property so later we use this info when * gvalue typecasting should be done */ (*constraint)->priv->klass = G_OBJECT_CLASS(*klass); (*constraint)->priv->propname = name; /* Reserved properties */ /* METADATA */ if (g_str_equal("metadata", name)) { *klass = g_type_class_peek(MIDGARD_TYPE_METADATA); return TRUE; } /* ATTACHMENT */ if (g_str_equal(_RESERVED_BLOB_NAME, name)) { if (builder && !builder->priv->blob_join_exists) { __join_reference(builder, (const gchar *)(*constraint)->priv->current->table, _RESERVED_BLOB_TABLE); builder->priv->blob_join_exists = TRUE; } *klass = g_type_class_peek(g_type_from_name("midgard_attachment")); (*constraint)->priv->current->table = _RESERVED_BLOB_TABLE; return TRUE; } /* PARAMETER */ if (g_str_equal(_RESERVED_PARAM_NAME, name)) { if (builder && !builder->priv->param_join_exists) { __join_reference(builder, (const gchar *)(*constraint)->priv->current->table, _RESERVED_PARAM_TABLE); builder->priv->param_join_exists = TRUE; } *klass = g_type_class_peek(g_type_from_name("midgard_parameter")); (*constraint)->priv->current->table = _RESERVED_PARAM_TABLE; /* Select DISTINCT. * Constraints might be grouped with "OR" operator and duplicated * records can be returned explicitly */ if (builder) { builder->priv->select_distinct = TRUE; } return TRUE; } /* User defined properties */ GParamSpec *pspec = g_object_class_find_property(G_OBJECT_CLASS(*klass), name); if(!pspec) { g_warning("%s is not registered property of %s class", name, G_OBJECT_CLASS_NAME(*klass)); return FALSE; } /* Set klass and property so later we use this info when gvalue typecasting should be done */ (*constraint)->priv->klass = G_OBJECT_CLASS(*klass); (*constraint)->priv->propname = pspec->name; (*constraint)->priv->pspec = pspec; attr = midgard_core_class_get_property_attr (MIDGARD_DBOBJECT_CLASS(*klass), name); target_property = NULL; link_property = NULL; MidgardReflectionProperty *mrp = midgard_reflection_property_new (MIDGARD_DBOBJECT_CLASS (*klass)); if (midgard_reflection_property_is_link (mrp, name) && do_link) { const gchar *target_property = midgard_reflection_property_get_link_target(mrp, name); const gchar *link_klass = midgard_reflection_property_get_link_name(mrp, name); gboolean is_link = TRUE; if ((*constraint)->priv->current->link_target == NULL) { MidgardCoreQueryConstraintPrivate *mqcp = midgard_core_query_constraint_private_new(); mqcp->current->is_link = is_link; mqcp->current->link_target = target_property; mqcp->condition_operator = g_strdup("="); mqcp->prop_left->table = midgard_core_class_get_property_table (MIDGARD_DBOBJECT_CLASS(*klass), name); mqcp->prop_left->field = midgard_core_class_get_property_colname (MIDGARD_DBOBJECT_CLASS(*klass), name); MidgardObjectClass *lclass = g_type_class_peek (g_type_from_name(link_klass)); mqcp->prop_right->table = midgard_core_class_get_property_table (MIDGARD_DBOBJECT_CLASS(lclass), target_property); mqcp->prop_right->field = midgard_core_class_get_property_colname (MIDGARD_DBOBJECT_CLASS(lclass), target_property); GString *cond = g_string_new (""); g_string_append_printf (cond, "%s.%s %s %s.%s", mqcp->prop_left->table, mqcp->prop_left->field, mqcp->condition_operator, mqcp->prop_right->table, mqcp->prop_right->field); mqcp->condition = g_string_free (cond, FALSE); if (builder) { midgard_core_qb_add_table(builder, mqcp->prop_left->table); midgard_core_qb_add_table(builder, mqcp->prop_right->table); } if (builder) { if(!__add_join(&builder->priv->joins, mqcp)) { midgard_core_query_constraint_private_free(mqcp); } } } *klass = (MidgardDBObjectClass *) midgard_reflection_property_get_link_class(mrp, name); (*constraint)->priv->current->table = midgard_core_class_get_table(MIDGARD_DBOBJECT_CLASS(*klass)); g_object_unref(mrp); return TRUE; } g_object_unref(mrp); const gchar *table, *field; if (g_str_equal (name, "guid")) { table = midgard_core_class_get_table(MIDGARD_DBOBJECT_CLASS(*klass)); field = "guid"; } else { table = attr->table; field = attr->field; } if(table != NULL) (*constraint)->priv->current->table = table; (*constraint)->priv->current->field = field; return TRUE; }