public function search($pn_subject_tablenum, $ps_search_expression, $pa_filters = array(), $po_rewritten_query = null) { $vn_i = 0; $va_old_signs = $po_rewritten_query->getSigns(); $va_terms = $va_signs = array(); foreach ($po_rewritten_query->getSubqueries() as $o_lucene_query_element) { switch ($vs_class = get_class($o_lucene_query_element)) { case 'Zend_Search_Lucene_Search_Query_Term': case 'Zend_Search_Lucene_Search_Query_MultiTerm': case 'Zend_Search_Lucene_Search_Query_Phrase': $vs_access_point = null; if ($vs_class != 'Zend_Search_Lucene_Search_Query_Term') { $va_raw_terms = array(); foreach ($o_lucene_query_element->getQueryTerms() as $o_term) { if (!$vs_access_point && ($vs_field = $o_term->field)) { $vs_access_point = $vs_field; } $va_raw_terms[] = $vs_text = (string) $o_term->text; } $vs_term = join(" ", $va_raw_terms); } else { $vs_access_point = $o_lucene_query_element->getTerm()->field; $vs_term = $o_lucene_query_element->getTerm()->text; $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('"' . $vs_term . '"', $vs_access_point)); } if ($vs_access_point) { list($vs_table, $vs_field, $vs_sub_field) = explode('.', $vs_access_point); if (in_array($vs_table, array('created', 'modified'))) { if (!$this->opo_tep->parse($vs_term)) { break; } $vn_user_id = null; if ($vs_field = trim($vs_field)) { if (!is_int($vs_field)) { $t_user = new ca_users(); if ($t_user->load(array("user_name" => $vs_field))) { $vn_user_id = (int) $t_user->getPrimaryKey(); } } else { $vn_user_id = (int) $vs_field; } } switch ($vs_table) { case 'created': if ($vn_user_id) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Boolean(array(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'created'), new Zend_Search_Lucene_Index_Term($vn_user_id, 'created_user_id')), array(true, true)); } else { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'created')); } break; case 'modified': if ($vn_user_id) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Boolean(array(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'modified'), new Zend_Search_Lucene_Index_Term($vn_user_id, 'modified_user_id')), array(true, true)); } else { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'modified')); } break; } } else { if ($vs_table && $vs_field) { $t_table = $this->opo_datamodel->getInstanceByTableName($vs_table, true); if (!$t_table) { break; } $vs_fld_num = $this->opo_datamodel->getFieldNum($vs_table, $vs_field); if (!$vs_fld_num) { // probably attribute $t_element = new ca_metadata_elements(); if ($t_element->load(array('element_code' => $vs_sub_field ? $vs_sub_field : $vs_field))) { // // For certain types of attributes we can directly query the // attributes in the database rather than using the full text index // This allows us to do "intelligent" querying... for example on date ranges // parsed from natural language input and for length dimensions using unit conversion // switch ($t_element->get('datatype')) { case 2: // dates $vb_exact = $vs_term[0] == "#" ? true : false; // dates prepended by "#" are considered "exact" or "contained - the matched dates must be wholly contained by the search term if ($vb_exact) { if ($this->opo_tep->parse($vs_term)) { // TODO: fix date handling to reflect distinctions in ranges $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', $vs_access_point)); } } else { if ($this->opo_tep->parse($vs_term)) { // TODO: fix date handling to reflect distinctions in ranges $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', $vs_access_point)); } } break; case 4: // geocode $t_geocode = new GeocodeAttributeValue(); if ($va_coords = caParseGISSearch($vs_term)) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $va_coords['min_latitude'] . ',' . $va_coords['min_longitude'] . " TO " . $va_coords['max_latitude'] . ',' . $va_coords['max_longitude'] . ']', $vs_access_point)); } break; case 6: // currency $t_cur = new CurrencyAttributeValue(); $va_parsed_value = $t_cur->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_amount = (double) $va_parsed_value['value_decimal1']; $vs_currency = preg_replace('![^A-Z0-9]+!', '', $va_parsed_value['value_longtext1']); $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_amount, $vs_access_point)); break; case 8: // length $t_len = new LengthAttributeValue(); $va_parsed_value = $t_len->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_len = (double) $va_parsed_value['value_decimal1']; // this is always in meters so we can compare this value to the one in the database $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_len, $vs_access_point)); break; case 9: // weight $t_weight = new WeightAttributeValue(); $va_parsed_value = $t_weight->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_weight = (double) $va_parsed_value['value_decimal1']; // this is always in kilograms so we can compare this value to the one in the database $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_weight, $vs_access_point)); break; case 10: // timecode $t_timecode = new TimecodeAttributeValue(); $va_parsed_value = $t_timecode->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_timecode = (double) $va_parsed_value['value_decimal1']; $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_timecode, $vs_access_point)); break; case 11: // integer $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term((double) $vs_term, $vs_access_point)); break; case 12: // decimal $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term((double) $vs_term, $vs_access_point)); break; } } } } } } break; } $va_terms[] = $o_lucene_query_element; $va_signs[] = is_array($va_old_signs) ? array_key_exists($vn_i, $va_old_signs) ? $va_old_signs[$vn_i] : true : true; $vn_i++; } $o_rewritten_query = new Zend_Search_Lucene_Search_Query_Boolean($va_terms, $va_signs); $ps_search_expression = $this->_queryToString($o_rewritten_query); if ($vs_filter_query = $this->_filterValueToQueryValue($pa_filters)) { $ps_search_expression = "({$ps_search_expression}) AND ({$vs_filter_query})"; } $vo_http_client = new Zend_Http_Client(); $vo_http_client->setUri($this->ops_elasticsearch_base_url . "/" . $this->ops_elasticsearch_index_name . "/" . $this->opo_datamodel->getTableName($pn_subject_tablenum) . "/" . "_search"); if (preg_match_all("!([A-Za-z0-9_\\-\\.]+)[/]{1}([A-Za-z0-9_\\-]+):(\"[^\"]*\")!", $ps_search_expression, $va_matches) || preg_match_all("!([A-Za-z0-9_\\-\\.]+)[/]{1}([A-Za-z0-9_\\-]+):([^ ]*)!", $ps_search_expression, $va_matches)) { foreach ($va_matches[0] as $vn_i => $vs_element) { $vs_fld = $va_matches[1][$vn_i]; if (!($vs_rel_type = trim($va_matches[2][$vn_i]))) { continue; } $va_tmp = explode(".", $vs_fld); $vs_rel_table = caGetRelationshipTableName($pn_subject_tablenum, $va_tmp[0]); $va_rel_type_ids = $vs_rel_type && $vs_rel_table ? caMakeRelationshipTypeIDList($vs_rel_table, array($vs_rel_type)) : null; $va_new_elements = array(); foreach ($va_rel_type_ids as $vn_rel_type_id) { $va_new_elements[] = "(" . $va_matches[1][$vn_i] . "/" . $vn_rel_type_id . ":" . $va_matches[3][$vn_i] . ")"; } $ps_search_expression = str_replace($vs_element, "(" . join(" OR ", $va_new_elements) . ")", $ps_search_expression); } } $ps_search_expression = str_replace("/", '\\/', $ps_search_expression); // escape forward slashes used to delimit relationship type qualifier Debug::msg("[ElasticSearch] Running query {$ps_search_expression}"); $vo_http_client->setParameterGet(array('size' => intval($this->opa_options["limit"]), 'q' => $ps_search_expression, 'fields' => '')); $vo_http_response = $vo_http_client->request(); $va_result = json_decode($vo_http_response->getBody(), true); return new WLPlugSearchEngineElasticSearchResult($va_result["hits"]["hits"], $pn_subject_tablenum); }
public function search($pn_subject_tablenum, $ps_search_expression, $pa_filters = array(), $po_rewritten_query = null) { $va_solr_search_filters = array(); $vn_i = 0; $va_old_signs = is_object($po_rewritten_query) ? $po_rewritten_query->getSigns() : array(); $va_terms = $va_signs = array(); if ($po_rewritten_query) { foreach ($po_rewritten_query->getSubqueries() as $o_lucene_query_element) { if (!$va_old_signs || !is_array($va_old_signs)) { // if array is null then according to Zend Lucene all subqueries should be "are required"... so we AND them $vs_op = "AND"; } else { if (is_null($va_old_signs[$vn_i])) { // is the sign for a particular query is null then OR is (it is "neither required nor prohibited") $vs_op = 'OR'; } else { $vs_op = $va_old_signs[$vn_i] === false ? 'NOT' : 'AND'; // true sign indicated "required" (AND) operation, false indicated "prohibited" (NOT) operation } } if ($vn_i == 0) { $vs_op = 'OR'; } // advanced search queries are for some reason nested 1-element boolean queries in boolean queries if (get_class($o_lucene_query_element) == 'Zend_Search_Lucene_Search_Query_Boolean') { $va_subqueries = $o_lucene_query_element->getSubqueries(); if (sizeof($va_subqueries) == 1) { $o_lucene_query_element = array_shift($va_subqueries); } } switch ($vs_class = get_class($o_lucene_query_element)) { case 'Zend_Search_Lucene_Search_Query_Term': case 'Zend_Search_Lucene_Search_Query_MultiTerm': case 'Zend_Search_Lucene_Search_Query_Phrase': $vs_access_point = ''; if ($vs_class != 'Zend_Search_Lucene_Search_Query_Term') { $va_raw_terms = array(); foreach ($o_lucene_query_element->getQueryTerms() as $o_term) { if (!$vs_access_point && ($vs_field = $o_term->field)) { $vs_access_point = $vs_field; } $va_raw_terms[] = $vs_text = (string) $o_term->text; } $vs_term = join(" ", $va_raw_terms); } else { $vs_access_point = $o_lucene_query_element->getTerm()->field; $vs_term = $o_lucene_query_element->getTerm()->text; } if ($vs_access_point) { list($vs_table, $vs_field, $vs_sub_field) = explode('.', $vs_access_point); if (in_array($vs_table, array('created', 'modified'))) { if (!$this->opo_tep->parse($vs_term)) { break; } $va_range = $this->opo_tep->getUnixTimestamps(); $vn_user_id = null; if ($vs_field = trim($vs_field)) { if (!is_int($vs_field)) { $t_user = new ca_users(); if ($t_user->load(array("user_name" => $vs_field))) { $vn_user_id = (int) $t_user->getPrimaryKey(); } } else { $vn_user_id = (int) $vs_field; } } $vs_user_sql = $vn_user_id ? " AND (ccl.user_id = " . (int) $vn_user_id . ")" : ""; switch ($vs_table) { case 'created': if ($vn_user_id) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Boolean(array(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'created'), new Zend_Search_Lucene_Index_Term($vn_user_id, 'created_user_id')), array(true, true)); } else { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'created')); } break; case 'modified': if ($vn_user_id) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Boolean(array(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'modified'), new Zend_Search_Lucene_Index_Term($vn_user_id, 'modified_user_id')), array(true, true)); } else { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', 'modified')); } break; } } else { if ($vs_table && $vs_field) { $t_table = $this->opo_datamodel->getInstanceByTableName($vs_table, true); if ($t_table) { $vs_table_num = $t_table->tableNum(); if (is_numeric($vs_field)) { $vs_fld_num = 'I' . $vs_field; $vn_fld_num = (int) $vs_field; } else { $vn_fld_num = $this->getFieldNum($vs_table, $vs_field); $vs_fld_num = 'I' . $vn_fld_num; if (!strlen($vn_fld_num)) { $t_element = new ca_metadata_elements(); if ($t_element->load(array('element_code' => $vs_sub_field ? $vs_sub_field : $vs_field))) { $vn_fld_num = $t_element->getPrimaryKey(); $vs_fld_num = 'A' . $vn_fld_num; // // For certain types of attributes we can directly query the // attributes in the database rather than using the full text index // This allows us to do "intelligent" querying... for example on date ranges // parsed from natural language input and for length dimensions using unit conversion // switch ($t_element->get('datatype')) { case 2: // dates $vb_exact = $vs_term[0] == "#" ? true : false; // dates prepended by "#" are considered "exact" or "contained - the matched dates must be wholly contained by the search term if ($vb_exact) { $vs_raw_term = substr($vs_term, 1); if ($this->opo_tep->parse($vs_term)) { $va_dates = $this->opo_tep->getHistoricTimestamps(); // TODO: fix date handling to reflect distinctions in ranges $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', $vs_table . '.' . $vs_fld_num)); } } else { if ($this->opo_tep->parse($vs_term)) { $va_dates = $this->opo_tep->getHistoricTimestamps(); // TODO: fix date handling to reflect distinctions in ranges $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $this->opo_tep->getText(array('start_as_iso8601' => true)) . " TO " . $this->opo_tep->getText(array('end_as_iso8601' => true)) . ']', $vs_table . '.' . $vs_fld_num)); } } break; case 4: // geocode $t_geocode = new GeocodeAttributeValue(); if ($va_coords = caParseGISSearch($vs_term)) { $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term('[' . $va_coords['min_latitude'] . ',' . $va_coords['min_longitude'] . " TO " . $va_coords['max_latitude'] . ',' . $va_coords['max_longitude'] . ']', $vs_table . '.' . $vs_fld_num)); } break; case 6: // currency $t_cur = new CurrencyAttributeValue(); $va_parsed_value = $t_cur->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_amount = (double) $va_parsed_value['value_decimal1']; $vs_currency = preg_replace('![^A-Z0-9]+!', '', $va_parsed_value['value_longtext1']); $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_amount, $vs_table . '.' . $vs_fld_num)); break; case 8: // length $t_len = new LengthAttributeValue(); $va_parsed_value = $t_len->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_len = (double) $va_parsed_value['value_decimal1']; // this is always in meters so we can compare this value to the one in the database $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_len, $vs_table . '.' . $vs_fld_num)); break; case 9: // weight $t_weight = new WeightAttributeValue(); $va_parsed_value = $t_weight->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_weight = (double) $va_parsed_value['value_decimal1']; // this is always in kilograms so we can compare this value to the one in the database $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_weight, $vs_table . '.' . $vs_fld_num)); break; case 10: // timecode $t_timecode = new TimecodeAttributeValue(); $va_parsed_value = $t_timecode->parseValue($vs_term, $t_element->getFieldValuesArray()); $vn_timecode = (double) $va_parsed_value['value_decimal1']; $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term($vn_timecode, $vs_table . '.' . $vs_fld_num)); break; case 11: // integer $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term((double) $vs_term, $vs_table . '.' . $vs_fld_num)); break; case 12: // decimal $o_lucene_query_element = new Zend_Search_Lucene_Search_Query_Term(new Zend_Search_Lucene_Index_Term((double) $vs_term, $vs_table . '.' . $vs_fld_num)); break; default: // everything else $o_lucene_query_element->getTerm()->field = $vs_table . '.' . $vs_fld_num; break; } } else { $vn_fld_num = false; $vs_fld_num = $vs_field; } } } } } } } break; } $va_terms[] = $o_lucene_query_element; $va_signs[] = is_array($va_old_signs) ? array_key_exists($vn_i, $va_old_signs) ? $va_old_signs[$vn_i] : true : true; $vn_i++; } $o_rewritten_query = new Zend_Search_Lucene_Search_Query_Boolean($va_terms, $va_signs); $ps_search_expression = $this->_queryToString($o_rewritten_query); } if (is_array($pa_filters) && sizeof($pa_filters) && ($vs_filter_query = $this->_filterValueToQueryValue($pa_filters))) { $ps_search_expression = "({$ps_search_expression}) AND ({$vs_filter_query})"; } if (preg_match_all("!([A-Za-z0-9_\\-\\.]+)[/]{1}([A-Za-z0-9_\\-]+):(\"[^\"]*\")!", $ps_search_expression, $va_matches) || preg_match_all("!([A-Za-z0-9_\\-\\.]+)[/]{1}([A-Za-z0-9_\\-]+):([^ ]*)!", $ps_search_expression, $va_matches)) { foreach ($va_matches[0] as $vn_i => $vs_element) { $vs_fld = $va_matches[1][$vn_i]; if (!($vs_rel_type = trim($va_matches[2][$vn_i]))) { continue; } $va_tmp = explode(".", $vs_fld); $vs_rel_table = caGetRelationshipTableName($pn_subject_tablenum, $va_tmp[0]); $va_rel_type_ids = $vs_rel_type && $vs_rel_table ? caMakeRelationshipTypeIDList($vs_rel_table, array($vs_rel_type)) : null; $va_new_elements = array(); foreach ($va_rel_type_ids as $vn_rel_type_id) { $va_new_elements[] = "(" . $va_matches[1][$vn_i] . "/" . $vn_rel_type_id . ":" . $va_matches[3][$vn_i] . ")"; } $ps_search_expression = str_replace($vs_element, "(" . join(" OR ", $va_new_elements) . ")", $ps_search_expression); } } $ps_search_expression = str_replace("/", '\\/', $ps_search_expression); // escape forward slashes used to delimit relationship type qualifier Debug::msg("[SOLR] Running query {$ps_search_expression}"); try { $vo_http_client = new Zend_Http_Client(); $vo_http_client->setUri($this->ops_search_solr_url . "/" . $this->opo_datamodel->getTableName($pn_subject_tablenum) . "/select"); $vo_http_client->setParameterGet($va_get = array('q' => utf8_decode($ps_search_expression), 'wt' => 'json', 'fl' => $this->opo_datamodel->getTablePrimaryKeyName($pn_subject_tablenum), 'start' => $this->getOption('start'), 'rows' => $this->getOption('limit'))); $vo_http_response = $vo_http_client->request(); $va_result = json_decode($vo_http_response->getBody(), true); } catch (Exception $e) { caLogEvent('ERR', _t('Could not connect to SOLR server: %1', $e->getMessage()), 'Solr->search()'); $va_result["response"]["docs"] = array(); } return new WLPlugSearchEngineSolrResult($va_result["response"]["docs"], $pn_subject_tablenum); }
private function _getElementIDForAccessPoint($pn_subject_tablenum, $ps_access_point) { $va_tmp = explode('/', $ps_access_point); list($vs_table, $vs_field, $vs_subfield) = explode('.', $va_tmp[0]); $vs_rel_table = caGetRelationshipTableName($pn_subject_tablenum, $vs_table); $va_rel_type_ids = $va_tmp[1] && $vs_rel_table ? caMakeRelationshipTypeIDList($vs_rel_table, array($va_tmp[1])) : null; if (!($t_table = $this->opo_datamodel->getInstanceByTableName($vs_table, true))) { return array('access_point' => $va_tmp[0]); } $vs_table_num = $t_table->tableNum(); if (is_numeric($vs_field)) { $vs_fld_num = $vs_field; } else { $vs_fld_num = $this->getFieldNum($vs_table, $vs_field); } if (!strlen($vs_fld_num)) { $t_element = new ca_metadata_elements(); if ($t_element->load(array('element_code' => $vs_subfield ? $vs_subfield : $vs_field))) { switch ($t_element->get('datatype')) { default: return array('access_point' => $va_tmp[0], 'relationship_type' => $va_tmp[1], 'table_num' => $vs_table_num, 'element_id' => $t_element->getPrimaryKey(), 'field_num' => 'A' . $t_element->getPrimaryKey(), 'datatype' => $t_element->get('datatype'), 'element_info' => $t_element->getFieldValuesArray(), 'relationship_type_ids' => $va_rel_type_ids); break; } } } else { return array('access_point' => $va_tmp[0], 'relationship_type' => $va_tmp[1], 'table_num' => $vs_table_num, 'field_num' => 'I' . $vs_fld_num, 'field_num_raw' => $vs_fld_num, 'datatype' => null, 'relationship_type_ids' => $va_rel_type_ids); } return null; }