diff --git a/flecs.c b/flecs.c index 6efe290905..3325854ff3 100644 --- a/flecs.c +++ b/flecs.c @@ -34765,6 +34765,7 @@ void flecs_rest_parse_json_ser_iter_params( flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); + flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "rows", &desc->serialize_rows); bool results = true; @@ -34992,13 +34993,19 @@ bool flecs_rest_reply_existing_query( } const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); - if (!poly || !poly->poly) { + if (!poly) { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } + if (!poly->poly) { + flecs_reply_error(reply, "query '%s' is not initialized", name); + reply->code = 400; + return true; + } + ecs_iter_t it; ecs_iter_poly(world, poly->poly, &it, NULL); @@ -35076,60 +35083,6 @@ bool flecs_rest_reply_query( return true; } -static -bool flecs_rest_reply_query_plan( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - const char *q_name = ecs_http_get_param(req, "name"); - if (q_name) { - reply->code = 400; - ecs_strbuf_appendlit(&reply->body, - "query plan endpoint unsupported for named queries"); - return true; - } - - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; - } - - bool try = false; - flecs_rest_bool_param(req, "try", &try); - - ecs_dbg_2("rest: request query plan for '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - rest_prev_log = ecs_os_api.log_; - ecs_os_api.log_ = flecs_rest_capture_log; - - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ - .expr = q - }); - if (!r) { - flecs_rest_reply_set_captured_log(reply); - if (try) { - /* If client is trying queries, don't spam console with errors */ - reply->code = 200; - } - } else { - ecs_log_enable_colors(true); - char *plan = ecs_rule_str(r); - ecs_strbuf_appendlit(&reply->body, "{\"content\":"); - flecs_json_string_escape(&reply->body, plan); - ecs_strbuf_appendlit(&reply->body, "}"); - ecs_os_free(plan); - ecs_rule_fini(r); - } - - ecs_os_api.log_ = rest_prev_log; - ecs_log_enable_colors(prev_color); - - return true; -} - #ifdef FLECS_MONITOR static @@ -35514,10 +35467,6 @@ bool flecs_rest_reply( } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_reply_query(world, req, reply); - /* Query plan endpoint */ - } else if (!ecs_os_strcmp(req->path, "query_plan")) { - return flecs_rest_reply_query_plan(world, req, reply); - /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_reply_world(world, req, reply); @@ -53285,6 +53234,49 @@ void flecs_json_serialize_query_info( flecs_json_serialize_query(world, q, buf); } +static +void flecs_json_serialize_query_plan( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + (void)world; + (void)it; + (void)buf; + +#ifdef FLECS_RULES + if (!it->query) { + return; + } + + /* Temporary hack to get rule object. Will no longer be necessary in v4 */ + ecs_iter_next_action_t next = it->next; + if (next == ecs_page_next) { + if (!it->chain_it) { + return; + } + + next = it->chain_it->next; + } + + if (next != ecs_rule_next) { + return; + } + + const ecs_filter_t *f = it->query; + const ecs_rule_t *q = ECS_OFFSET(f, -ECS_SIZEOF(ecs_header_t)); + ecs_poly_assert(q, ecs_rule_t); + + flecs_json_memberl(buf, "query_plan"); + + bool prev_color = ecs_log_enable_colors(true); + char *plan = ecs_rule_str(q); + flecs_json_string_escape(buf, plan); + ecs_os_free(plan); + ecs_log_enable_colors(prev_color); +#endif +} + static void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; @@ -54030,6 +54022,11 @@ int ecs_iter_to_json_buf( flecs_json_serialize_query_info(world, it, buf); } + /* Serialize query plan if enabled */ + if (desc && desc->serialize_query_plan) { + flecs_json_serialize_query_plan(world, it, buf); + } + /* Serialize results */ if (!desc || !desc->dont_serialize_results) { flecs_json_memberl(buf, "results"); diff --git a/flecs.h b/flecs.h index 39cfc2d754..b0ee7940e7 100644 --- a/flecs.h +++ b/flecs.h @@ -12997,6 +12997,7 @@ typedef struct ecs_iter_to_json_desc_t { bool serialize_rows; /**< Use row-based serialization, with entities in separate elements */ bool serialize_field_info; /**< Serialize metadata for fields returned by query */ bool serialize_query_info; /**< Serialize query terms */ + bool serialize_query_plan; /**< Serialize query plan */ bool dont_serialize_results; /**< If true, query won't be evaluated */ } ecs_iter_to_json_desc_t; @@ -13022,6 +13023,7 @@ typedef struct ecs_iter_to_json_desc_t { .serialize_rows = false, \ .serialize_field_info = false, \ .serialize_query_info = false, \ + .serialize_query_plan = false, \ .dont_serialize_results = false, \ } diff --git a/include/flecs/addons/json.h b/include/flecs/addons/json.h index 621620af43..deb1c5b8a4 100644 --- a/include/flecs/addons/json.h +++ b/include/flecs/addons/json.h @@ -286,6 +286,7 @@ typedef struct ecs_iter_to_json_desc_t { bool serialize_rows; /**< Use row-based serialization, with entities in separate elements */ bool serialize_field_info; /**< Serialize metadata for fields returned by query */ bool serialize_query_info; /**< Serialize query terms */ + bool serialize_query_plan; /**< Serialize query plan */ bool dont_serialize_results; /**< If true, query won't be evaluated */ } ecs_iter_to_json_desc_t; @@ -311,6 +312,7 @@ typedef struct ecs_iter_to_json_desc_t { .serialize_rows = false, \ .serialize_field_info = false, \ .serialize_query_info = false, \ + .serialize_query_plan = false, \ .dont_serialize_results = false, \ } diff --git a/src/addons/json/serialize.c b/src/addons/json/serialize.c index 4ccd51962f..f3ccb48ad7 100644 --- a/src/addons/json/serialize.c +++ b/src/addons/json/serialize.c @@ -1511,6 +1511,49 @@ void flecs_json_serialize_query_info( flecs_json_serialize_query(world, q, buf); } +static +void flecs_json_serialize_query_plan( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf) +{ + (void)world; + (void)it; + (void)buf; + +#ifdef FLECS_RULES + if (!it->query) { + return; + } + + /* Temporary hack to get rule object. Will no longer be necessary in v4 */ + ecs_iter_next_action_t next = it->next; + if (next == ecs_page_next) { + if (!it->chain_it) { + return; + } + + next = it->chain_it->next; + } + + if (next != ecs_rule_next) { + return; + } + + const ecs_filter_t *f = it->query; + const ecs_rule_t *q = ECS_OFFSET(f, -ECS_SIZEOF(ecs_header_t)); + ecs_poly_assert(q, ecs_rule_t); + + flecs_json_memberl(buf, "query_plan"); + + bool prev_color = ecs_log_enable_colors(true); + char *plan = ecs_rule_str(q); + flecs_json_string_escape(buf, plan); + ecs_os_free(plan); + ecs_log_enable_colors(prev_color); +#endif +} + static void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; @@ -2256,6 +2299,11 @@ int ecs_iter_to_json_buf( flecs_json_serialize_query_info(world, it, buf); } + /* Serialize query plan if enabled */ + if (desc && desc->serialize_query_plan) { + flecs_json_serialize_query_plan(world, it, buf); + } + /* Serialize results */ if (!desc || !desc->dont_serialize_results) { flecs_json_memberl(buf, "results"); diff --git a/src/addons/rest.c b/src/addons/rest.c index cbc40d3b9d..00a882969d 100644 --- a/src/addons/rest.c +++ b/src/addons/rest.c @@ -193,6 +193,7 @@ void flecs_rest_parse_json_ser_iter_params( flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); + flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "rows", &desc->serialize_rows); bool results = true; @@ -420,13 +421,19 @@ bool flecs_rest_reply_existing_query( } const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); - if (!poly || !poly->poly) { + if (!poly) { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } + if (!poly->poly) { + flecs_reply_error(reply, "query '%s' is not initialized", name); + reply->code = 400; + return true; + } + ecs_iter_t it; ecs_iter_poly(world, poly->poly, &it, NULL); @@ -504,60 +511,6 @@ bool flecs_rest_reply_query( return true; } -static -bool flecs_rest_reply_query_plan( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - const char *q_name = ecs_http_get_param(req, "name"); - if (q_name) { - reply->code = 400; - ecs_strbuf_appendlit(&reply->body, - "query plan endpoint unsupported for named queries"); - return true; - } - - const char *q = ecs_http_get_param(req, "q"); - if (!q) { - ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); - reply->code = 400; /* bad request */ - return true; - } - - bool try = false; - flecs_rest_bool_param(req, "try", &try); - - ecs_dbg_2("rest: request query plan for '%s'", q); - bool prev_color = ecs_log_enable_colors(false); - rest_prev_log = ecs_os_api.log_; - ecs_os_api.log_ = flecs_rest_capture_log; - - ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ - .expr = q - }); - if (!r) { - flecs_rest_reply_set_captured_log(reply); - if (try) { - /* If client is trying queries, don't spam console with errors */ - reply->code = 200; - } - } else { - ecs_log_enable_colors(true); - char *plan = ecs_rule_str(r); - ecs_strbuf_appendlit(&reply->body, "{\"content\":"); - flecs_json_string_escape(&reply->body, plan); - ecs_strbuf_appendlit(&reply->body, "}"); - ecs_os_free(plan); - ecs_rule_fini(r); - } - - ecs_os_api.log_ = rest_prev_log; - ecs_log_enable_colors(prev_color); - - return true; -} - #ifdef FLECS_MONITOR static @@ -942,10 +895,6 @@ bool flecs_rest_reply( } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_reply_query(world, req, reply); - /* Query plan endpoint */ - } else if (!ecs_os_strcmp(req->path, "query_plan")) { - return flecs_rest_reply_query_plan(world, req, reply); - /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_reply_world(world, req, reply); diff --git a/test/addons/project.json b/test/addons/project.json index ce75112ff6..3c093b98c5 100644 --- a/test/addons/project.json +++ b/test/addons/project.json @@ -1854,7 +1854,6 @@ "get_cached", "get_cached_invalid", "try_query", - "try_query_plan", "query", "named_query" ] diff --git a/test/addons/src/Rest.c b/test/addons/src/Rest.c index 9e82cad2ca..3856a85d7a 100644 --- a/test/addons/src/Rest.c +++ b/test/addons/src/Rest.c @@ -141,35 +141,6 @@ void Rest_try_query(void) { ecs_fini(world); } -void Rest_try_query_plan(void) { - ecs_world_t *world = ecs_init(); - - ecs_http_server_t *srv = ecs_rest_server_init(world, NULL); - test_assert(srv != NULL); - - ecs_log_set_level(-4); - - { - ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - test_int(-1, ecs_http_server_request(srv, "GET", - "/query_plan?q=Foo", &reply)); - test_int(reply.code, 400); // No try, should error - ecs_strbuf_reset(&reply.body); - } - - { - ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; - test_int(0, ecs_http_server_request(srv, "GET", - "/query_plan?q=Foo&try=true", &reply)); - test_int(reply.code, 200); // With try, should not error - ecs_strbuf_reset(&reply.body); - } - - ecs_rest_server_fini(srv); - - ecs_fini(world); -} - void Rest_query(void) { ecs_world_t *world = ecs_init(); diff --git a/test/addons/src/main.c b/test/addons/src/main.c index 483eaa1083..12154c80f1 100644 --- a/test/addons/src/main.c +++ b/test/addons/src/main.c @@ -1783,7 +1783,6 @@ void Rest_get(void); void Rest_get_cached(void); void Rest_get_cached_invalid(void); void Rest_try_query(void); -void Rest_try_query_plan(void); void Rest_query(void); void Rest_named_query(void); @@ -8764,10 +8763,6 @@ bake_test_case Rest_testcases[] = { "try_query", Rest_try_query }, - { - "try_query_plan", - Rest_try_query_plan - }, { "query", Rest_query @@ -9313,7 +9308,7 @@ static bake_test_suite suites[] = { "Rest", NULL, NULL, - 8, + 7, Rest_testcases }, {