From d8485bf3c2ff3c3da4f39bffb4973baa61037a5d Mon Sep 17 00:00:00 2001 From: Drew Thompson Date: Mon, 25 Aug 2025 12:14:31 -0500 Subject: [PATCH] [scan] Add support for "empty" operand in smart playlists "empty" will be parsed to SQL NULL --- docs/smart-playlists.md | 14 ++++++++++++++ src/parsers/smartpl_lexer.l | 1 + src/parsers/smartpl_parser.y | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/docs/smart-playlists.md b/docs/smart-playlists.md index 45c7cfd9..3409e093 100644 --- a/docs/smart-playlists.md +++ b/docs/smart-playlists.md @@ -63,6 +63,9 @@ Valid operands include: * "string value" (string) * integer (int) +* `empty` + +The `empty` operand is only valid with the `is` operator and matches items with no value for the given field-name e.g. `comment` Valid operands for the enumeration `data_kind` are: @@ -155,6 +158,17 @@ This would match the last 10 music files added to the library. This generates a random set of, maximum of 10, rated Pop music tracks every time the playlist is queried. +``` +"All Jazz, No Foo" { + media_kind is music and + genre is "jazz" and + (not comment includes "foo" or + comment is empty) +} +``` + +This matches both the songs with comments that do not include "foo", but also the songs with no comment. + ## Date Operand Syntax One example of a valid date is a date in yyyy-mm-dd format: diff --git a/src/parsers/smartpl_lexer.l b/src/parsers/smartpl_lexer.l index bfb45520..d1947045 100644 --- a/src/parsers/smartpl_lexer.l +++ b/src/parsers/smartpl_lexer.l @@ -150,6 +150,7 @@ ends\ with { return (yylval->ival = SMARTPL_T_ENDSWITH); } or { return SMARTPL_T_OR; } and { return SMARTPL_T_AND; } not { return SMARTPL_T_NOT; } +empty { return SMARTPL_T_EMPTY; } {quoted} { yylval->str=strdup(yytext+1); if(yylval->str[strlen(yylval->str)-1] == '"') diff --git a/src/parsers/smartpl_parser.y b/src/parsers/smartpl_parser.y index 2d3bc422..17120221 100644 --- a/src/parsers/smartpl_parser.y +++ b/src/parsers/smartpl_parser.y @@ -229,6 +229,7 @@ enum sql_append_type { SQL_APPEND_FIELD, SQL_APPEND_STR, SQL_APPEND_INT, + SQL_APPEND_NULL, SQL_APPEND_ORDER, SQL_APPEND_PARENS, SQL_APPEND_DATE_STRFTIME, @@ -339,6 +340,11 @@ static void sql_append_recursive(struct smartpl_result *result, struct result_pa assert(a->r == NULL); sql_append(result, part, "%d", a->ival); break; + case SQL_APPEND_NULL: + assert(a->l == NULL); + assert(a->r == NULL); + sql_append(result, part, "NULL", NULL); + break; case SQL_APPEND_ORDER: assert(a->l == NULL); assert(a->r == NULL); @@ -397,6 +403,8 @@ static void sql_from_ast(struct smartpl_result *result, struct result_part *part sql_append_recursive(result, part, a, ">=", "<", is_not, SQL_APPEND_OPERATOR); break; case SMARTPL_T_IS: sql_append_recursive(result, part, a, "=", "!=", is_not, SQL_APPEND_OPERATOR_STR); break; + case SMARTPL_T_IS_OPERATOR: + sql_append_recursive(result, part, a, "IS", "IS NOT", is_not, SQL_APPEND_OPERATOR); break; case SMARTPL_T_INCLUDES: case SMARTPL_T_STARTSWITH: case SMARTPL_T_ENDSWITH: @@ -437,6 +445,8 @@ static void sql_from_ast(struct smartpl_result *result, struct result_part *part sql_append_recursive(result, part, a, NULL, NULL, 0, SQL_APPEND_FIELD); break; case SMARTPL_T_NUM: sql_append_recursive(result, part, a, NULL, NULL, 0, SQL_APPEND_INT); break; + case SMARTPL_T_NULL: + sql_append_recursive(result, part, a, NULL, NULL, 0, SQL_APPEND_NULL); break; case SMARTPL_T_ORDERBY: sql_append_recursive(result, part, a, "ASC", "DESC", is_not, SQL_APPEND_ORDER); break; case SMARTPL_T_RANDOM: @@ -518,6 +528,9 @@ static int result_set(struct smartpl_result *result, char *title, struct ast *cr %token SMARTPL_T_OR %token SMARTPL_T_AND %token SMARTPL_T_NOT +%token SMARTPL_T_EMPTY +%token SMARTPL_T_NULL +%token SMARTPL_T_IS_OPERATOR %token SMARTPL_T_DAYS %token SMARTPL_T_WEEKS @@ -590,6 +603,7 @@ criteria: criteria SMARTPL_T_AND criteria { $$ = ast_new(SMART predicate: SMARTPL_T_STRTAG strbool SMARTPL_T_STRING { $$ = ast_new($2, ast_data(SMARTPL_T_STRTAG, $1), ast_data(SMARTPL_T_STRING, $3)); } | SMARTPL_T_INTTAG intbool SMARTPL_T_NUM { $$ = ast_new($2, ast_data(SMARTPL_T_INTTAG, $1), ast_int(SMARTPL_T_NUM, $3)); } | SMARTPL_T_DATETAG datebool dateexpr { $$ = ast_new($2, ast_data(SMARTPL_T_DATETAG, $1), $3); } +| SMARTPL_T_STRTAG SMARTPL_T_IS SMARTPL_T_EMPTY { $$ = ast_new(SMARTPL_T_IS_OPERATOR, ast_data(SMARTPL_T_STRTAG, $1), ast_data(SMARTPL_T_NULL, NULL)); } | enumexpr ;