void ExtEngineManager::Trigger::execute(thread_db* tdbb, ExternalTrigger::Action action, record_param* oldRpb, record_param* newRpb) const { EngineAttachmentInfo* attInfo = extManager->getEngineAttachment(tdbb, engine); ContextManager<ExternalTrigger> ctxManager(tdbb, attInfo, trigger, CallerName(obj_trigger, trg->name)); // ASF: Using Array instead of HalfStaticArray to not need to do alignment hacks here. Array<UCHAR> oldMsg; Array<UCHAR> newMsg; if (oldRpb) setValues(tdbb, oldMsg, oldRpb); if (newRpb) setValues(tdbb, newMsg, newRpb); { // scope Attachment::Checkout attCout(tdbb->getAttachment(), FB_FUNCTION); LocalStatus status; trigger->execute(&status, attInfo->context, action, (oldMsg.hasData() ? oldMsg.begin() : NULL), (newMsg.hasData() ? newMsg.begin() : NULL)); status.check(); } if (newRpb) { // Move data back from the message to the record. Record* record = newRpb->rpb_record; UCHAR* p = newMsg.begin(); for (unsigned i = 0; i < format->fmt_count / 2u; ++i) { USHORT fieldPos = fieldsPos[i]; dsc target; bool readonly = !EVL_field(newRpb->rpb_relation, record, fieldPos, &target) && target.dsc_address && !(target.dsc_flags & DSC_null); if (!readonly) { SSHORT* nullSource = (SSHORT*) (p + (IPTR) format->fmt_desc[i * 2 + 1].dsc_address); if (*nullSource == 0) { dsc source = format->fmt_desc[i * 2]; source.dsc_address += (IPTR) p; MOV_move(tdbb, &source, &target); record->clearNull(fieldPos); } else record->setNull(fieldPos); } } } }
bool AggregatedStream::getRecord(thread_db* tdbb) const { if (--tdbb->tdbb_quantum < 0) JRD_reschedule(tdbb, 0, true); jrd_req* const request = tdbb->getRequest(); record_param* const rpb = &request->req_rpb[m_stream]; Impure* const impure = request->getImpure<Impure>(m_impure); if (!(impure->irsb_flags & irsb_open)) { rpb->rpb_number.setValid(false); return false; } if (m_bufferedStream) // Is that a window stream? { const FB_UINT64 position = m_bufferedStream->getPosition(request); if (impure->pending == 0) { if (impure->state == STATE_PENDING) { if (!m_bufferedStream->getRecord(tdbb)) fb_assert(false); } impure->state = evaluateGroup(tdbb, impure->state); if (impure->state == STATE_PROCESS_EOF) { rpb->rpb_number.setValid(false); return false; } impure->pending = m_bufferedStream->getPosition(request) - position - (impure->state == STATE_EOF_FOUND ? 0 : 1); m_bufferedStream->locate(tdbb, position); } if (m_winPassSources.hasData()) { SlidingWindow window(tdbb, m_bufferedStream, m_group, request); dsc* desc; const NestConst<ValueExprNode>* const sourceEnd = m_winPassSources.end(); for (const NestConst<ValueExprNode>* source = m_winPassSources.begin(), *target = m_winPassTargets.begin(); source != sourceEnd; ++source, ++target) { const AggNode* aggNode = (*source)->as<AggNode>(); const FieldNode* field = (*target)->as<FieldNode>(); const USHORT id = field->fieldId; Record* record = request->req_rpb[field->fieldStream].rpb_record; desc = aggNode->winPass(tdbb, request, &window); if (!desc) record->setNull(id); else { MOV_move(tdbb, desc, EVL_assign_to(tdbb, *target)); record->clearNull(id); } } } if (impure->pending > 0) --impure->pending; if (!m_bufferedStream->getRecord(tdbb)) { rpb->rpb_number.setValid(false); return false; } // If there is no group, we should reassign the map items. if (!m_group) { const NestConst<ValueExprNode>* const sourceEnd = m_map->sourceList.end(); for (const NestConst<ValueExprNode>* source = m_map->sourceList.begin(), *target = m_map->targetList.begin(); source != sourceEnd; ++source, ++target) { const AggNode* aggNode = (*source)->as<AggNode>(); if (!aggNode) EXE_assignment(tdbb, *source, *target); } } } else { impure->state = evaluateGroup(tdbb, impure->state); if (impure->state == STATE_PROCESS_EOF) { rpb->rpb_number.setValid(false); return false; } } rpb->rpb_number.setValid(true); return true; }
// Compute the next aggregated record of a value group. evlGroup is driven by, and returns, a state // variable. AggregatedStream::State AggregatedStream::evaluateGroup(thread_db* tdbb, State state) const { jrd_req* const request = tdbb->getRequest(); if (--tdbb->tdbb_quantum < 0) JRD_reschedule(tdbb, 0, true); Impure* const impure = request->getImpure<Impure>(m_impure); // if we found the last record last time, we're all done if (state == STATE_EOF_FOUND) return STATE_PROCESS_EOF; try { const NestConst<ValueExprNode>* const sourceEnd = m_map->sourceList.end(); // If there isn't a record pending, open the stream and get one if (!m_order || state == STATE_PROCESS_EOF || state == STATE_GROUPING) { // Initialize the aggregate record for (const NestConst<ValueExprNode>* source = m_map->sourceList.begin(), *target = m_map->targetList.begin(); source != sourceEnd; ++source, ++target) { const AggNode* aggNode = (*source)->as<AggNode>(); if (aggNode) aggNode->aggInit(tdbb, request); else if ((*source)->is<LiteralNode>()) EXE_assignment(tdbb, *source, *target); } } if (state == STATE_PROCESS_EOF || state == STATE_GROUPING) { if (!m_next->getRecord(tdbb)) { if (m_group) { finiDistinct(tdbb, request); return STATE_PROCESS_EOF; } state = STATE_EOF_FOUND; } } cacheValues(tdbb, request, m_group, 0); if (state != STATE_EOF_FOUND) cacheValues(tdbb, request, m_order, (m_group ? m_group->getCount() : 0)); // Loop thru records until either a value change or EOF while (state != STATE_EOF_FOUND) { state = STATE_PENDING; // go through and compute all the aggregates on this record for (const NestConst<ValueExprNode>* source = m_map->sourceList.begin(), *target = m_map->targetList.begin(); source != sourceEnd; ++source, ++target) { const AggNode* aggNode = (*source)->as<AggNode>(); if (aggNode) { if (aggNode->aggPass(tdbb, request)) { // If a max or min has been mapped to an index, then the first record is the EOF. if (aggNode->indexed) state = STATE_EOF_FOUND; } } else EXE_assignment(tdbb, *source, *target); } if (state != STATE_EOF_FOUND && m_next->getRecord(tdbb)) { // In the case of a group by, look for a change in value of any of // the columns; if we find one, stop aggregating and return what we have. if (lookForChange(tdbb, request, m_group, 0)) { if (m_order) state = STATE_GROUPING; break; } if (lookForChange(tdbb, request, m_order, (m_group ? m_group->getCount() : 0))) break; } else state = STATE_EOF_FOUND; } // Finish up any residual computations and get out for (const NestConst<ValueExprNode>* source = m_map->sourceList.begin(), *target = m_map->targetList.begin(); source != sourceEnd; ++source, ++target) { const AggNode* aggNode = (*source)->as<AggNode>(); if (aggNode) { const FieldNode* field = (*target)->as<FieldNode>(); const USHORT id = field->fieldId; Record* record = request->req_rpb[field->fieldStream].rpb_record; dsc* desc = aggNode->execute(tdbb, request); if (!desc || !desc->dsc_dtype) record->setNull(id); else { MOV_move(tdbb, desc, EVL_assign_to(tdbb, *target)); record->clearNull(id); } } } } catch (const Exception&) { finiDistinct(tdbb, request); throw; } return state; }
// Perform an assignment. void EXE_assignment(thread_db* tdbb, const ValueExprNode* to, dsc* from_desc, bool from_null, const ValueExprNode* missing_node, const ValueExprNode* missing2_node) { SET_TDBB(tdbb); jrd_req* request = tdbb->getRequest(); // Get descriptors of receiving and sending fields/parameters, variables, etc. dsc* missing = NULL; if (missing_node) missing = EVL_expr(tdbb, request, missing_node); // Get descriptor of target field/parameter/variable, etc. DSC* to_desc = EVL_assign_to(tdbb, to); request->req_flags &= ~req_null; // NS: If we are assigning to NULL, we finished. // This functionality is currently used to allow calling UDF routines // without assigning resulting value anywhere. if (!to_desc) return; SSHORT null = from_null ? -1 : 0; if (!null && missing && MOV_compare(missing, from_desc) == 0) null = -1; USHORT* impure_flags = NULL; const ParameterNode* toParam; const VariableNode* toVar; if ((toParam = ExprNode::as<ParameterNode>(to))) { const MessageNode* message = toParam->message; if (toParam->argInfo) { EVL_validate(tdbb, Item(Item::TYPE_PARAMETER, message->messageNumber, toParam->argNumber), toParam->argInfo, from_desc, null == -1); } impure_flags = request->getImpure<USHORT>( message->impureFlags + (sizeof(USHORT) * toParam->argNumber)); } else if ((toVar = ExprNode::as<VariableNode>(to))) { if (toVar->varInfo) { EVL_validate(tdbb, Item(Item::TYPE_VARIABLE, toVar->varId), toVar->varInfo, from_desc, null == -1); } impure_flags = &request->getImpure<impure_value>( toVar->varDecl->impureOffset)->vlu_flags; } if (impure_flags != NULL) *impure_flags |= VLU_checked; // If the value is non-missing, move/convert it. Otherwise fill the // field with appropriate nulls. dsc temp; if (!null) { // if necessary and appropriate, use the indicator variable if (toParam && toParam->argIndicator) { dsc* indicator = EVL_assign_to(tdbb, toParam->argIndicator); temp.dsc_dtype = dtype_short; temp.dsc_length = sizeof(SSHORT); temp.dsc_scale = 0; temp.dsc_sub_type = 0; SSHORT len; if ((from_desc->dsc_dtype <= dtype_varying) && (to_desc->dsc_dtype <= dtype_varying) && (TEXT_LEN(from_desc) > TEXT_LEN(to_desc))) { len = TEXT_LEN(from_desc); } else len = 0; temp.dsc_address = (UCHAR *) &len; MOV_move(tdbb, &temp, indicator); if (len) { temp = *from_desc; temp.dsc_length = TEXT_LEN(to_desc); if (temp.dsc_dtype == dtype_cstring) temp.dsc_length += 1; else if (temp.dsc_dtype == dtype_varying) temp.dsc_length += 2; from_desc = &temp; } } // Validate range for datetime values if (DTYPE_IS_DATE(from_desc->dsc_dtype)) { switch (from_desc->dsc_dtype) { case dtype_sql_date: if (!Firebird::TimeStamp::isValidDate(*(GDS_DATE*) from_desc->dsc_address)) { ERR_post(Arg::Gds(isc_date_range_exceeded)); } break; case dtype_sql_time: if (!Firebird::TimeStamp::isValidTime(*(GDS_TIME*) from_desc->dsc_address)) { ERR_post(Arg::Gds(isc_time_range_exceeded)); } break; case dtype_timestamp: if (!Firebird::TimeStamp::isValidTimeStamp(*(GDS_TIMESTAMP*) from_desc->dsc_address)) { ERR_post(Arg::Gds(isc_datetime_range_exceeded)); } break; default: fb_assert(false); } } if (DTYPE_IS_BLOB_OR_QUAD(from_desc->dsc_dtype) || DTYPE_IS_BLOB_OR_QUAD(to_desc->dsc_dtype)) { // ASF: Don't let MOV_move call blb::move because MOV // will not pass the destination field to blb::_move. blb::move(tdbb, from_desc, to_desc, to); } else if (!DSC_EQUIV(from_desc, to_desc, false)) { MOV_move(tdbb, from_desc, to_desc); } else if (from_desc->dsc_dtype == dtype_short) { *((SSHORT*) to_desc->dsc_address) = *((SSHORT*) from_desc->dsc_address); } else if (from_desc->dsc_dtype == dtype_long) { *((SLONG*) to_desc->dsc_address) = *((SLONG*) from_desc->dsc_address); } else if (from_desc->dsc_dtype == dtype_int64) { *((SINT64*) to_desc->dsc_address) = *((SINT64*) from_desc->dsc_address); } else { memcpy(to_desc->dsc_address, from_desc->dsc_address, from_desc->dsc_length); } to_desc->dsc_flags &= ~DSC_null; } else { if (missing2_node && (missing = EVL_expr(tdbb, request, missing2_node))) MOV_move(tdbb, missing, to_desc); else memset(to_desc->dsc_address, 0, to_desc->dsc_length); to_desc->dsc_flags |= DSC_null; } // Handle the null flag as appropriate for fields and message arguments. const FieldNode* toField = ExprNode::as<FieldNode>(to); if (toField) { Record* record = request->req_rpb[toField->fieldStream].rpb_record; if (null) record->setNull(toField->fieldId); else record->clearNull(toField->fieldId); } else if (toParam && toParam->argFlag) { to_desc = EVL_assign_to(tdbb, toParam->argFlag); // If the null flag is a string with an effective length of one, // then -1 will not fit. Therefore, store 1 instead. if (null && to_desc->dsc_dtype <= dtype_varying) { USHORT minlen; switch (to_desc->dsc_dtype) { case dtype_text: minlen = 1; break; case dtype_cstring: minlen = 2; break; case dtype_varying: minlen = 3; break; } if (to_desc->dsc_length <= minlen) null = 1; } temp.dsc_dtype = dtype_short; temp.dsc_length = sizeof(SSHORT); temp.dsc_scale = 0; temp.dsc_sub_type = 0; temp.dsc_address = (UCHAR*) &null; MOV_move(tdbb, &temp, to_desc); if (null && toParam->argIndicator) { to_desc = EVL_assign_to(tdbb, toParam->argIndicator); MOV_move(tdbb, &temp, to_desc); } } }