/* * Copyright (c) 2010, Swedish Institute of Computer Science * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /** * \file * Logic for relational databases. * \author * Nicolas Tsiftes */ #include #include #include "lib/crc16.h" #include "lib/list.h" #include "lib/memb.h" #define DEBUG DEBUG_NONE #include "net/ip/uip-debug.h" #include "db-options.h" #include "index.h" #include "lvm.h" #include "relation.h" #include "result.h" #include "storage.h" #include "aql.h" /* * The source_dest_map structure is used for mapping the pointers to * data in a source row and in the corresponding destination row. The * structure is calculated just before processing a relational * selection, and then used to improve the performance when processing * each row. */ struct source_dest_map { attribute_t *from_attr; attribute_t *to_attr; unsigned from_offset; unsigned to_offset; }; static struct source_dest_map attr_map[AQL_ATTRIBUTE_LIMIT]; #if DB_FEATURE_JOIN /* * The source_map structure is used for mapping attributes to * their offsets in rows. */ struct source_map { attribute_t *attr; unsigned char *from_ptr; }; static struct source_map source_map[AQL_ATTRIBUTE_LIMIT]; #endif /* DB_FEATURE_JOIN */ static unsigned char row[DB_MAX_ATTRIBUTES_PER_RELATION * DB_MAX_ELEMENT_SIZE]; static unsigned char extra_row[DB_MAX_ATTRIBUTES_PER_RELATION * DB_MAX_ELEMENT_SIZE]; static unsigned char result_row[AQL_ATTRIBUTE_LIMIT * DB_MAX_ELEMENT_SIZE]; static unsigned char * const left_row = row; static unsigned char * const right_row = extra_row; static unsigned char * const join_row = result_row; LIST(relations); MEMB(relations_memb, relation_t, DB_RELATION_POOL_SIZE); MEMB(attributes_memb, attribute_t, DB_ATTRIBUTE_POOL_SIZE); static relation_t *relation_find(char *); static attribute_t *attribute_find(relation_t *, char *); static int get_attribute_value_offset(relation_t *, attribute_t *); static void attribute_free(relation_t *, attribute_t *); static void purge_relations(void); static void relation_clear(relation_t *); static relation_t *relation_allocate(void); static void relation_free(relation_t *); static relation_t * relation_find(char *name) { relation_t *rel; for(rel = list_head(relations); rel != NULL; rel = rel->next) { if(strcmp(rel->name, name) == 0) { return rel; } } return NULL; } static attribute_t * attribute_find(relation_t *rel, char *name) { attribute_t *attr; for(attr = list_head(rel->attributes); attr != NULL; attr = attr->next) { if(strcmp(attr->name, name) == 0) { return attr; } } return NULL; } static int get_attribute_value_offset(relation_t *rel, attribute_t *attr) { attribute_t *ptr; int offset; for(offset = 0, ptr = list_head(rel->attributes); ptr != NULL; ptr = ptr->next) { if(ptr == attr) { return offset; } offset += ptr->element_size; } return -1; } static void attribute_free(relation_t *rel, attribute_t *attr) { if(attr->index != NULL) { index_release(attr->index); } memb_free(&attributes_memb, attr); rel->attribute_count--; } static void purge_relations(void) { relation_t *rel; relation_t *next; for(rel = list_head(relations); rel != NULL;) { next = rel->next; if(rel->references == 0) { relation_free(rel); } rel = next; } } static void relation_clear(relation_t *rel) { memset(rel, 0, sizeof(*rel)); rel->tuple_storage = -1; rel->cardinality = INVALID_TUPLE; rel->dir = DB_STORAGE; LIST_STRUCT_INIT(rel, attributes); } static relation_t * relation_allocate(void) { relation_t *rel; rel = memb_alloc(&relations_memb); if(rel == NULL) { purge_relations(); rel = memb_alloc(&relations_memb); if(rel == NULL) { PRINTF("DB: Failed to allocate a relation\n"); return NULL; } } relation_clear(rel); return rel; } static void relation_free(relation_t *rel) { attribute_t *attr; while((attr = list_pop(rel->attributes)) != NULL) { attribute_free(rel, attr); } list_remove(relations, rel); memb_free(&relations_memb, rel); } db_result_t relation_init(void) { list_init(relations); memb_init(&relations_memb); memb_init(&attributes_memb); return DB_OK; } relation_t * relation_load(char *name) { relation_t *rel; rel = relation_find(name); if(rel != NULL) { rel->references++; goto end; } rel = relation_allocate(); if(rel == NULL) { return NULL; } if(DB_ERROR(storage_get_relation(rel, name))) { memb_free(&relations_memb, rel); return NULL; } memcpy(rel->name, name, sizeof(rel->name)); rel->name[sizeof(rel->name) - 1] = '\0'; rel->references = 1; list_add(relations, rel); end: if(rel->dir == DB_STORAGE && DB_ERROR(storage_load(rel))) { relation_release(rel); return NULL; } return rel; } db_result_t relation_release(relation_t *rel) { if(rel->references > 0) { rel->references--; } if(rel->references == 0) { storage_unload(rel); } return DB_OK; } relation_t * relation_create(char *name, db_direction_t dir) { relation_t old_rel; relation_t *rel; if(*name != '\0') { relation_clear(&old_rel); if(storage_get_relation(&old_rel, name) == DB_OK) { /* Reject a creation request if the relation already exists. */ PRINTF("DB: Attempted to create a relation that already exists (%s)\n", name); return NULL; } rel = relation_allocate(); if(rel == NULL) { return NULL; } rel->cardinality = 0; strncpy(rel->name, name, sizeof(rel->name) - 1); rel->name[sizeof(rel->name) - 1] = '\0'; rel->dir = dir; if(dir == DB_STORAGE) { storage_drop_relation(rel, 1); if(storage_put_relation(rel) == DB_OK) { list_add(relations, rel); return rel; } memb_free(&relations_memb, rel); } else { list_add(relations, rel); return rel; } } return NULL; } #if DB_FEATURE_REMOVE db_result_t relation_rename(char *old_name, char *new_name) { if(DB_ERROR(relation_remove(new_name, 0)) || DB_ERROR(storage_rename_relation(old_name, new_name))) { return DB_STORAGE_ERROR; } return DB_OK; } #endif /* DB_FEATURE_REMOVE */ attribute_t * relation_attribute_add(relation_t *rel, db_direction_t dir, char *name, domain_t domain, size_t element_size) { attribute_t *attribute; tuple_id_t cardinality; cardinality = relation_cardinality(rel); if(cardinality != INVALID_TUPLE && cardinality > 0) { PRINTF("DB: Attempt to create an attribute in a non-empty relation\n"); return NULL; } if(element_size == 0 || element_size > DB_MAX_ELEMENT_SIZE) { PRINTF("DB: Unacceptable element size: %u\n", element_size); return NULL; } attribute = memb_alloc(&attributes_memb); if(attribute == NULL) { PRINTF("DB: Failed to allocate attribute \"%s\"!\n", name); return NULL; } strncpy(attribute->name, name, sizeof(attribute->name) - 1); attribute->name[sizeof(attribute->name) - 1] = '\0'; attribute->domain = domain; attribute->element_size = element_size; attribute->aggregator = 0; attribute->index = NULL; attribute->flags = 0 /*ATTRIBUTE_FLAG_UNIQUE*/; rel->row_length += element_size; list_add(rel->attributes, attribute); rel->attribute_count++; if(dir == DB_STORAGE) { if(DB_ERROR(storage_put_attribute(rel, attribute))) { PRINTF("DB: Failed to store attribute %s\n", attribute->name); memb_free(&attributes_memb, attribute); return NULL; } } else { index_load(rel, attribute); } return attribute; } attribute_t * relation_attribute_get(relation_t *rel, char *name) { attribute_t *attr; attr = attribute_find(rel, name); if(attr != NULL && !(attr->flags & ATTRIBUTE_FLAG_INVALID)) { return attr; } return NULL; } db_result_t relation_attribute_remove(relation_t *rel, char *name) { /* Not implemented completely. */ return DB_IMPLEMENTATION_ERROR; #if 0 attribute_t *attr; if(rel->references > 1) { return DB_BUSY_ERROR; } attr = relation_attribute_get(rel, name); if(attr == NULL) { return DB_NAME_ERROR; } list_remove(rel->attributes, attr); attribute_free(rel, attr); return DB_OK; #endif } db_result_t relation_get_value(relation_t *rel, attribute_t *attr, unsigned char *row_ptr, attribute_value_t *value) { int offset; unsigned char *from_ptr; offset = get_attribute_value_offset(rel, attr); if(offset < 0) { return DB_IMPLEMENTATION_ERROR; } from_ptr = row_ptr + offset; return db_phy_to_value(value, attr, from_ptr); } db_result_t relation_set_primary_key(relation_t *rel, char *name) { attribute_t *attribute; attribute = relation_attribute_get(rel, name); if(attribute == NULL) { return DB_NAME_ERROR; } attribute->flags = ATTRIBUTE_FLAG_PRIMARY_KEY; PRINTF("DB: New primary key: %s\n", attribute->name); return DB_OK; } db_result_t relation_remove(char *name, int remove_tuples) { relation_t *rel; db_result_t result; rel = relation_load(name); if(rel == NULL) { /* * Attempt to remove an inexistent relation. To allow for this * operation to be used for setting up repeatable tests and * experiments, we do not signal an error. */ return DB_OK; } if(rel->references > 1) { return DB_BUSY_ERROR; } result = storage_drop_relation(rel, remove_tuples); relation_free(rel); return result; } db_result_t relation_insert(relation_t *rel, attribute_value_t *values) { attribute_t *attr; unsigned char record[rel->row_length]; unsigned char *ptr; attribute_value_t *value; db_result_t result; value = values; PRINTF("DB: Relation %s has a record size of %u bytes\n", rel->name, (unsigned)rel->row_length); ptr = record; PRINTF("DB: Insert ("); for(attr = list_head(rel->attributes); attr != NULL; attr = attr->next, value++) { /* Verify that the value is in the expected domain. An exception to this rule is that INT may be promoted to LONG. */ if(attr->domain != value->domain && !(attr->domain == DOMAIN_LONG && value->domain == DOMAIN_INT)) { PRINTF("DB: The value domain %d does not match the domain %d of attribute %s\n", value->domain, attr->domain, attr->name); return DB_RELATIONAL_ERROR; } /* Set the data area for removed attributes to 0. */ if(attr->flags & ATTRIBUTE_FLAG_INVALID) { memset(ptr, 0, attr->element_size); ptr += attr->element_size; continue; } result = db_value_to_phy((unsigned char *)ptr, attr, value); if(DB_ERROR(result)) { return result; } #if DEBUG switch(attr->domain) { case DOMAIN_INT: PRINTF("%s=%d", attr->name, VALUE_INT(value)); break; case DOMAIN_LONG: PRINTF("%s=%ld", attr->name, VALUE_LONG(value)); break; case DOMAIN_STRING: PRINTF("%s='%s", attr->name, VALUE_STRING(value)); break; default: PRINTF(")\nDB: Unhandled attribute domain: %d\n", attr->domain); return DB_TYPE_ERROR; } if(attr->next != NULL) { PRINTF(", "); } #endif /* DEBUG */ ptr += attr->element_size; if(attr->index != NULL) { if(DB_ERROR(index_insert(attr->index, value, rel->next_row))) { return DB_INDEX_ERROR; } } } PRINTF(")\n"); rel->cardinality++; rel->next_row++; return storage_put_row(rel, record); } static void aggregate(attribute_t *attr, attribute_value_t *value) { long long_value; switch(value->domain) { case DOMAIN_INT: long_value = VALUE_INT(value); break; case DOMAIN_LONG: long_value = VALUE_LONG(value); break; default: return; } switch(attr->aggregator) { case AQL_COUNT: attr->aggregation_value++; break; case AQL_SUM: attr->aggregation_value += long_value; break; case AQL_MEAN: break; case AQL_MEDIAN: break; case AQL_MAX: if(long_value > attr->aggregation_value) { attr->aggregation_value = long_value; } break; case AQL_MIN: if(long_value < attr->aggregation_value) { attr->aggregation_value = long_value; } break; default: break; } } static db_result_t generate_attribute_map(struct source_dest_map *attr_map, unsigned attribute_count, relation_t *from_rel, relation_t *to_rel, unsigned char *from_row, unsigned char *to_row) { attribute_t *from_attr; attribute_t *to_attr; unsigned size_sum; struct source_dest_map *attr_map_ptr; int offset; attr_map_ptr = attr_map; for(size_sum = 0, to_attr = list_head(to_rel->attributes); to_attr != NULL; to_attr = to_attr->next) { from_attr = attribute_find(from_rel, to_attr->name); if(from_attr == NULL) { PRINTF("DB: Invalid attribute in the result relation: %s\n", to_attr->name); return DB_NAME_ERROR; } attr_map_ptr->from_attr = from_attr; attr_map_ptr->to_attr = to_attr; offset = get_attribute_value_offset(from_rel, from_attr); if(offset < 0) { return DB_IMPLEMENTATION_ERROR; } attr_map_ptr->from_offset = offset; attr_map_ptr->to_offset = size_sum; size_sum += to_attr->element_size; attr_map_ptr++; } return DB_OK; } static void select_index(db_handle_t *handle, lvm_instance_t *lvm_instance) { index_t *index; attribute_t *attr; operand_value_t min; operand_value_t max; attribute_value_t av_min; attribute_value_t av_max; long range; unsigned long min_range; index = NULL; min_range = ULONG_MAX; /* Find all indexed and derived attributes, and select the index of the attribute with the smallest range. */ for(attr = list_head(handle->rel->attributes); attr != NULL; attr = attr->next) { if(attr->index != NULL && !LVM_ERROR(lvm_get_derived_range(lvm_instance, attr->name, &min, &max))) { range = (unsigned long)max.l - (unsigned long)min.l; PRINTF("DB: The search range for attribute \"%s\" comprises %ld values\n", attr->name, range + 1); if(range <= min_range) { index = attr->index; av_min.domain = av_max.domain = DOMAIN_INT; VALUE_LONG(&av_min) = min.l; VALUE_LONG(&av_max) = max.l; } } } if(index != NULL) { /* We found a suitable index; get an iterator for it. */ if(index_get_iterator(&handle->index_iterator, index, &av_min, &av_max) == DB_OK) { handle->flags |= DB_HANDLE_FLAG_SEARCH_INDEX; } } } static db_result_t generate_selection_result(db_handle_t *handle, relation_t *rel, aql_adt_t *adt) { relation_t *result_rel; unsigned attribute_count; attribute_t *attr; result_rel = handle->result_rel; handle->current_row = 0; handle->ncolumns = 0; handle->tuple_id = 0; for(attr = list_head(result_rel->attributes); attr != NULL; attr = attr->next) { if(attr->flags & ATTRIBUTE_FLAG_NO_STORE) { continue; } handle->ncolumns++; } handle->tuple = (tuple_t)result_row; attribute_count = result_rel->attribute_count; if(DB_ERROR(generate_attribute_map(attr_map, attribute_count, rel, result_rel, row, result_row))) { return DB_IMPLEMENTATION_ERROR; } if(adt->lvm_instance != NULL) { /* Try to establish acceptable ranges for the attribute values. */ if(!LVM_ERROR(lvm_derive(adt->lvm_instance))) { select_index(handle, adt->lvm_instance); } } handle->flags |= DB_HANDLE_FLAG_PROCESSING; return DB_OK; } #if DB_FEATURE_REMOVE db_result_t relation_process_remove(void *handle_ptr) { db_handle_t *handle; aql_adt_t *adt; db_result_t result; handle = (db_handle_t *)handle_ptr; adt = handle->adt; result = relation_process_select(handle_ptr); if(result == DB_FINISHED) { PRINTF("DB: Finished removing tuples. Overwriting relation %s with the result\n", adt->relations[1]); relation_release(handle->rel); relation_rename(adt->relations[0], adt->relations[1]); } return result; } #endif db_result_t relation_process_select(void *handle_ptr) { db_handle_t *handle; aql_adt_t *adt; db_result_t result; unsigned attribute_count; struct source_dest_map *attr_map_ptr, *attr_map_end; attribute_t *result_attr; unsigned char *from_ptr; unsigned char *to_ptr; operand_value_t operand_value; uint8_t intbuf[2]; attribute_value_t value; lvm_status_t wanted_result; handle = (db_handle_t *)handle_ptr; adt = (aql_adt_t *)handle->adt; attribute_count = handle->result_rel->attribute_count; attr_map_end = attr_map + attribute_count; if(handle->flags & DB_HANDLE_FLAG_SEARCH_INDEX) { handle->tuple_id = index_get_next(&handle->index_iterator); if(handle->tuple_id == INVALID_TUPLE) { PRINTF("DB: An attribute value could not be found in the index\n"); if(handle->index_iterator.next_item_no == 0) { return DB_INDEX_ERROR; } if(adt->flags & AQL_FLAG_AGGREGATE) { goto end_aggregation; } return DB_FINISHED; } } /* Put the tuples fulfilling the given condition into a new relation. The tuples may be projected. */ result = storage_get_row(handle->rel, &handle->tuple_id, row); handle->tuple_id++; if(DB_ERROR(result)) { PRINTF("DB: Failed to get a row in relation %s!\n", handle->rel->name); return result; } else if(result == DB_FINISHED) { if(AQL_GET_FLAGS(adt) & AQL_FLAG_AGGREGATE) { goto end_aggregation; } return DB_FINISHED; } /* Process the attributes in the result relation. */ for(attr_map_ptr = attr_map; attr_map_ptr < attr_map_end; attr_map_ptr++) { from_ptr = row + attr_map_ptr->from_offset; result_attr = attr_map_ptr->to_attr; /* Update the internal state of the PLE. */ if(result_attr->domain == DOMAIN_INT) { operand_value.l = from_ptr[0] << 8 | from_ptr[1]; lvm_set_variable_value(result_attr->name, operand_value); } else if(result_attr->domain == DOMAIN_LONG) { operand_value.l = (uint32_t)from_ptr[0] << 24 | (uint32_t)from_ptr[1] << 16 | (uint32_t)from_ptr[2] << 8 | from_ptr[3]; lvm_set_variable_value(result_attr->name, operand_value); } if(result_attr->flags & ATTRIBUTE_FLAG_NO_STORE) { /* The attribute is used just for the predicate, so do not copy the current value into the result. */ continue; } if(!(AQL_GET_FLAGS(adt) & AQL_FLAG_AGGREGATE)) { /* No aggregators. Copy the original value into the resulting tuple. */ memcpy(result_row + attr_map_ptr->to_offset, from_ptr, result_attr->element_size); } } wanted_result = TRUE; if(AQL_GET_FLAGS(adt) & AQL_FLAG_INVERSE_LOGIC) { wanted_result = FALSE; } /* Check whether the given predicate is true for this tuple. */ if(adt->lvm_instance == NULL || lvm_execute(adt->lvm_instance) == wanted_result) { if(AQL_GET_FLAGS(adt) & AQL_FLAG_AGGREGATE) { for(attr_map_ptr = attr_map; attr_map_ptr < attr_map_end; attr_map_ptr++) { from_ptr = row + attr_map_ptr->from_offset; result = db_phy_to_value(&value, attr_map_ptr->to_attr, from_ptr); if(DB_ERROR(result)) { return result; } aggregate(attr_map_ptr->to_attr, &value); } } else { if(AQL_GET_FLAGS(adt) & AQL_FLAG_ASSIGN) { if(DB_ERROR(storage_put_row(handle->result_rel, result_row))) { PRINTF("DB: Failed to store a row in the result relation!\n"); return DB_STORAGE_ERROR; } } handle->current_row++; return DB_GOT_ROW; } } return DB_OK; end_aggregation: /* Generate aggregated result if requested. */ for(attr_map_ptr = attr_map; attr_map_ptr < attr_map_end; attr_map_ptr++) { result_attr = attr_map_ptr->to_attr; to_ptr = result_row + attr_map_ptr->to_offset; intbuf[0] = result_attr->aggregation_value >> 8; intbuf[1] = result_attr->aggregation_value & 0xff; from_ptr = intbuf; memcpy(to_ptr, from_ptr, result_attr->element_size); } if(AQL_GET_FLAGS(adt) & AQL_FLAG_ASSIGN) { if(DB_ERROR(storage_put_row(handle->result_rel, result_row))) { PRINTF("DB: Failed to store a row in the result relation!\n"); return DB_STORAGE_ERROR; } } handle->current_row = 1; AQL_GET_FLAGS(adt) &= ~AQL_FLAG_AGGREGATE; /* Stop the aggregation. */ return DB_GOT_ROW; } db_result_t relation_select(void *handle_ptr, relation_t *rel, void *adt_ptr) { aql_adt_t *adt; db_handle_t *handle; char *name; db_direction_t dir; char *attribute_name; attribute_t *attr; int i; int normal_attributes; adt = (aql_adt_t *)adt_ptr; handle = (db_handle_t *)handle_ptr; handle->rel = rel; handle->adt = adt; if(AQL_GET_FLAGS(adt) & AQL_FLAG_ASSIGN) { name = adt->relations[0]; dir = DB_STORAGE; } else { name = RESULT_RELATION; dir = DB_MEMORY; } relation_remove(name, 1); relation_create(name, dir); handle->result_rel = relation_load(name); if(handle->result_rel == NULL) { PRINTF("DB: Failed to load a relation for the query result\n"); return DB_ALLOCATION_ERROR; } for(i = normal_attributes = 0; i < AQL_ATTRIBUTE_COUNT(adt); i++) { attribute_name = adt->attributes[i].name; attr = relation_attribute_get(rel, attribute_name); if(attr == NULL) { PRINTF("DB: Select for invalid attribute %s in relation %s!\n", attribute_name, rel->name); return DB_NAME_ERROR; } PRINTF("DB: Found attribute %s in relation %s\n", attribute_name, rel->name); attr = relation_attribute_add(handle->result_rel, dir, attribute_name, adt->aggregators[i] ? DOMAIN_INT : attr->domain, attr->element_size); if(attr == NULL) { PRINTF("DB: Failed to add a result attribute\n"); relation_release(handle->result_rel); return DB_ALLOCATION_ERROR; } attr->aggregator = adt->aggregators[i]; switch(attr->aggregator) { case AQL_NONE: if(!(adt->attributes[i].flags & ATTRIBUTE_FLAG_NO_STORE)) { /* Only count attributes projected into the result set. */ normal_attributes++; } break; case AQL_MAX: attr->aggregation_value = LONG_MIN; break; case AQL_MIN: attr->aggregation_value = LONG_MAX; break; default: attr->aggregation_value = 0; break; } attr->flags = adt->attributes[i].flags; } /* Preclude mixes of normal attributes and aggregated ones in selection results. */ if(normal_attributes > 0 && handle->result_rel->attribute_count > normal_attributes) { return DB_RELATIONAL_ERROR; } return generate_selection_result(handle, rel, adt); } #if DB_FEATURE_JOIN db_result_t relation_process_join(void *handle_ptr) { db_handle_t *handle; db_result_t result; relation_t *left_rel; relation_t *right_rel; relation_t *join_rel; unsigned char *join_next_attribute_ptr; size_t element_size; tuple_id_t right_tuple_id; attribute_value_t value; int i; handle = (db_handle_t *)handle_ptr; left_rel = handle->left_rel; right_rel = handle->right_rel; join_rel = handle->join_rel; if(!(handle->flags & DB_HANDLE_FLAG_INDEX_STEP)) { goto inner_loop; } /* Equi-join for indexed attributes only. In the outer loop, we iterate over each tuple in the left relation. */ for(handle->tuple_id = 0;; handle->tuple_id++) { result = storage_get_row(left_rel, &handle->tuple_id, left_row); if(DB_ERROR(result)) { PRINTF("DB: Failed to get a row in left relation %s!\n", left_rel->name); return result; } else if(result == DB_FINISHED) { return DB_FINISHED; } if(DB_ERROR(relation_get_value(left_rel, handle->left_join_attr, left_row, &value))) { PRINTF("DB: Failed to get a value of the attribute \"%s\" to join on\n", handle->left_join_attr->name); return DB_IMPLEMENTATION_ERROR; } if(DB_ERROR(index_get_iterator(&handle->index_iterator, handle->right_join_attr->index, &value, &value))) { PRINTF("DB: Failed to get an index iterator\n"); return DB_INDEX_ERROR; } handle->flags &= ~DB_HANDLE_FLAG_INDEX_STEP; /* In the inner loop, we iterate over all rows with a matching value for the join attribute. The index component provides an iterator for this purpose. */ inner_loop: for(;;) { /* Get all rows matching the attribute value in the right relation. */ right_tuple_id = index_get_next(&handle->index_iterator); if(right_tuple_id == INVALID_TUPLE) { /* Exclude this row from the left relation in the result, and step to the next value in the index iteration. */ handle->flags |= DB_HANDLE_FLAG_INDEX_STEP; break; } result = storage_get_row(right_rel, &right_tuple_id, right_row); if(DB_ERROR(result)) { PRINTF("DB: Failed to get a row in right relation %s!\n", right_rel->name); return result; } else if(result == DB_FINISHED) { PRINTF("DB: The index refers to an invalid row: %lu\n", (unsigned long)right_tuple_id); return DB_IMPLEMENTATION_ERROR; } /* Use the source attribute map to fill in the physical representation of the resulting tuple. */ join_next_attribute_ptr = join_row; for(i = 0; i < join_rel->attribute_count; i++) { element_size = source_map[i].attr->element_size; memcpy(join_next_attribute_ptr, source_map[i].from_ptr, element_size); join_next_attribute_ptr += element_size; } if(((aql_adt_t *)handle->adt)->flags & AQL_FLAG_ASSIGN) { if(DB_ERROR(storage_put_row(join_rel, join_row))) { return DB_STORAGE_ERROR; } } handle->current_row++; return DB_GOT_ROW; } } return DB_OK; } static db_result_t generate_join_result(db_handle_t *handle) { relation_t *left_rel; relation_t *right_rel; relation_t *join_rel; attribute_t *attr; attribute_t *result_attr; struct source_map *source_pair; int i; int offset; unsigned char *from_ptr; handle->tuple = (tuple_t)join_row; handle->tuple_id = 0; left_rel = handle->left_rel; right_rel = handle->right_rel; join_rel = handle->join_rel; /* Generate a map over the source attributes for each attribute in the join relation. */ for(i = 0, result_attr = list_head(join_rel->attributes); result_attr != NULL; result_attr = result_attr->next, i++) { source_pair = &source_map[i]; attr = attribute_find(left_rel, result_attr->name); if(attr != NULL) { offset = get_attribute_value_offset(left_rel, attr); from_ptr = left_row + offset; } else if((attr = attribute_find(right_rel, result_attr->name)) != NULL) { offset = get_attribute_value_offset(right_rel, attr); from_ptr = right_row + offset; } else { PRINTF("DB: The attribute %s could not be found\n", result_attr->name); return DB_NAME_ERROR; } if(offset < 0) { PRINTF("DB: Unable to retrieve attribute values for the JOIN result\n"); return DB_IMPLEMENTATION_ERROR; } source_pair->attr = attr; source_pair->from_ptr = from_ptr; } handle->flags |= DB_HANDLE_FLAG_PROCESSING; return DB_OK; } db_result_t relation_join(void *query_result, void *adt_ptr) { aql_adt_t *adt; db_handle_t *handle; relation_t *left_rel; relation_t *right_rel; relation_t *join_rel; char *name; db_direction_t dir; int i; char *attribute_name; attribute_t *attr; adt = (aql_adt_t *)adt_ptr; handle = (db_handle_t *)query_result; handle->current_row = 0; handle->ncolumns = 0; handle->adt = adt; handle->flags = DB_HANDLE_FLAG_INDEX_STEP; if(AQL_GET_FLAGS(adt) & AQL_FLAG_ASSIGN) { name = adt->relations[0]; dir = DB_STORAGE; } else { name = RESULT_RELATION; dir = DB_MEMORY; } relation_remove(name, 1); relation_create(name, dir); join_rel = relation_load(name); handle->result_rel = join_rel; if(join_rel == NULL) { PRINTF("DB: Failed to create a join relation!\n"); return DB_ALLOCATION_ERROR; } handle->join_rel = handle->result_rel = join_rel; left_rel = handle->left_rel; right_rel = handle->right_rel; handle->left_join_attr = relation_attribute_get(left_rel, adt->attributes[0].name); handle->right_join_attr = relation_attribute_get(right_rel, adt->attributes[0].name); if(handle->left_join_attr == NULL || handle->right_join_attr == NULL) { PRINTF("DB: The attribute (\"%s\") to join on does not exist in both relations\n", adt->attributes[0].name); return DB_RELATIONAL_ERROR; } if(!index_exists(handle->right_join_attr)) { PRINTF("DB: The attribute to join on is not indexed\n"); return DB_INDEX_ERROR; } /* * Define the resulting relation. We start from 1 when counting attributes * because the first attribute is only the one to join, and is not included * by default in the projected attributes. */ for(i = 1; i < AQL_ATTRIBUTE_COUNT(adt); i++) { attribute_name = adt->attributes[i].name; attr = relation_attribute_get(left_rel, attribute_name); if(attr == NULL) { attr = relation_attribute_get(right_rel, attribute_name); if(attr == NULL) { PRINTF("DB: The projection attribute \"%s\" does not exist in any of the relations to join\n", attribute_name); return DB_RELATIONAL_ERROR; } } if(relation_attribute_add(join_rel, dir, attr->name, attr->domain, attr->element_size) == NULL) { PRINTF("DB: Failed to add an attribute to the join relation\n"); return DB_ALLOCATION_ERROR; } handle->ncolumns++; } return generate_join_result(handle); } #endif /* DB_FEATURE_JOIN */ tuple_id_t relation_cardinality(relation_t *rel) { tuple_id_t tuple_id; if(rel->cardinality != INVALID_TUPLE) { return rel->cardinality; } if(!RELATION_HAS_TUPLES(rel)) { return 0; } if(DB_ERROR(storage_get_row_amount(rel, &tuple_id))) { return INVALID_TUPLE; } rel->cardinality = tuple_id; PRINTF("DB: Relation %s has cardinality %lu\n", rel->name, (unsigned long)tuple_id); return tuple_id; }