Fossil SCM

Add the artifact_to_json(rid|symbolic-name) UDF. Emit check-ins with no P- or F-card as having an empty array for those cards, in the hope of simplifying host-language traversal over the results (no need to check for existence of the cards before traversal).

stephan 2025-03-24 17:06 artifact-to-json
Commit 4416f09b420cae1486a4c02e72b954fa7204dc3308d90a3ba3f75bffefacab2d
2 files changed +2 +81 -12
+2
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
16291629
sqlite3_create_function(db, "chat_msg_from_event", 4,
16301630
SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
16311631
chat_msg_from_event, 0, 0);
16321632
sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
16331633
file_inode_sql_func,0,0);
1634
+ sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635
+ artifact_to_json_sql_func,0,0);
16341636
16351637
}
16361638
16371639
#if USE_SEE
16381640
/*
16391641
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
 
 
1634
1635 }
1636
1637 #if USE_SEE
1638 /*
1639
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
1634 sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635 artifact_to_json_sql_func,0,0);
1636
1637 }
1638
1639 #if USE_SEE
1640 /*
1641
+81 -12
--- src/manifest.c
+++ src/manifest.c
@@ -2931,11 +2931,14 @@
29312931
}
29322932
return NULL;
29332933
}
29342934
29352935
/*
2936
-** Creates a JSON representation of p, appending it to pOut.
2936
+** Creates a JSON representation of p, appending it to b.
2937
+**
2938
+** b is not cleared before rendering, so the caller needs to do that
2939
+** if it's important for their use case.
29372940
**
29382941
** Pedantic note: this routine traverses p->aFile directly, rather than
29392942
** using manifest_file_next(), so that delta manifests are rendered as-is
29402943
** instead of having their derived files. If that policy is ever changed,
29412944
** p will need to be non-const.
@@ -2991,10 +2994,13 @@
29912994
KVP_STR(1, uuid, pF->zUuid);
29922995
KVP_STR(1, perm, pF->zPerm);
29932996
KVP_STR(1, oldName, pF->zPrior);
29942997
blob_append_char(b, '}');
29952998
}
2999
+ /* Special case: model checkins with no F-card as having an empty
3000
+ ** array, rather than no F-cards, to hypothetically simplify
3001
+ ** handling in JSON queries. */
29963002
blob_append_char(b, ']');
29973003
}
29983004
CARD_STR2(G, p->zThreadRoot);
29993005
CARD_STR2(H, p->zThreadTitle);
30003006
CARD_STR2(I, p->zInReplyTo);
@@ -3020,17 +3026,22 @@
30203026
blob_appendf(b, "%!j", p->azCChild[i]);
30213027
}
30223028
blob_append_char(b, ']');
30233029
}
30243030
CARD_STR2(N, p->zMimetype);
3025
- if( p->nParent ){
3031
+ ISA( CFTYPE_MANIFEST ){
30263032
CARD_LETTER(P);
30273033
blob_append_char(b, '[');
3028
- for( i = 0; i < p->nParent; ++i ){
3029
- if( i>0 ) blob_append_char(b, ',');
3030
- blob_appendf(b, "%!j", p->azParent[i]);
3034
+ if( p->nParent ){
3035
+ for( i = 0; i < p->nParent; ++i ){
3036
+ if( i>0 ) blob_append_char(b, ',');
3037
+ blob_appendf(b, "%!j", p->azParent[i]);
3038
+ }
30313039
}
3040
+ /* Special case: model checkins with no P-card as having
3041
+ ** an empty array, rather than no P-card, to hypothetically
3042
+ ** simplify handling in JSON queries. */
30323043
blob_append_char(b, ']');
30333044
}
30343045
if( p->nCherrypick ){
30353046
CARD_LETTER(Q);
30363047
blob_append_char(b, '[');
@@ -3075,10 +3086,26 @@
30753086
#undef CARD_STR2
30763087
#undef ISA
30773088
#undef KVP_STR
30783089
#undef STR_OR_NULL
30793090
}
3091
+
3092
+/*
3093
+** Convenience wrapper around artifact_to_json() which expects rid to
3094
+** be the blob.rid of any artifact type. If it can load a Manifest
3095
+** with that rid, it returns rid, else it returns 0.
3096
+*/
3097
+int artifact_to_json_by_rid(int rid, Blob *pOut){
3098
+ Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3099
+ if( p ){
3100
+ artifact_to_json(p, pOut);
3101
+ manifest_destroy(p);
3102
+ }else{
3103
+ rid = 0;
3104
+ }
3105
+ return rid;
3106
+}
30803107
30813108
/*
30823109
** Convenience wrapper around artifact_to_json() which accepts any
30833110
** artifact name which is legal for symbolic_name_to_rid(). On success
30843111
** it returns the rid of the artifact. Returns 0 if no such artifact
@@ -3086,21 +3113,63 @@
30863113
**
30873114
** pOut is not cleared before rendering, so the caller needs to do
30883115
** that if it's important for their use case.
30893116
*/
30903117
int artifact_to_json_by_name(const char *zName, Blob *pOut){
3091
- int rid;
3118
+ const int rid = symbolic_name_to_rid(zName, 0);
3119
+ return rid>0
3120
+ ? artifact_to_json_by_rid(rid, pOut)
3121
+ : rid;
3122
+}
3123
+
3124
+/*
3125
+** SQLite UDF for artifact_to_json(). Its single argument should be
3126
+** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3127
+** name, as per symbolic_name_to_rid(). If an artifact is found then
3128
+** the result of the UDF is that JSON as a string, else it evaluates
3129
+** to NULL.
3130
+*/
3131
+void artifact_to_json_sql_func(
3132
+ sqlite3_context *context,
3133
+ int argc,
3134
+ sqlite3_value **argv
3135
+){
3136
+ int rid = 0;
3137
+ Blob b = empty_blob;
30923138
3093
- rid = symbolic_name_to_rid(zName, 0);
3139
+ if(1 != argc){
3140
+ goto error_usage;
3141
+ }
3142
+ switch( sqlite3_value_type(argv[0]) ){
3143
+ case SQLITE_INTEGER:
3144
+ rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3145
+ break;
3146
+ case SQLITE_TEXT:{
3147
+ const char * z = (const char *)sqlite3_value_text(argv[0]);
3148
+ if( z ){
3149
+ rid = artifact_to_json_by_name(z, &b);
3150
+ }
3151
+ break;
3152
+ }
3153
+ default:
3154
+ goto error_usage;
3155
+ }
30943156
if( rid>0 ){
3095
- Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3096
- assert(p && "Is it possible to fail this if rid is a phantom?");
3097
- artifact_to_json(p, pOut);
3098
- manifest_destroy(p);
3157
+ sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3158
+ SQLITE_TRANSIENT);
3159
+ blob_reset(&b);
3160
+ }else{
3161
+ /* We should arguably error out if rid<0 (ambiguous name) */
3162
+ sqlite3_result_null(context);
30993163
}
3100
- return rid;
3164
+ return;
3165
+error_usage:
3166
+ sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3167
+ "artifact symbolic name", -1);
31013168
}
3169
+
3170
+
31023171
31033172
/*
31043173
** COMMAND: test-artifact-to-json
31053174
**
31063175
** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
31073176
--- src/manifest.c
+++ src/manifest.c
@@ -2931,11 +2931,14 @@
2931 }
2932 return NULL;
2933 }
2934
2935 /*
2936 ** Creates a JSON representation of p, appending it to pOut.
 
 
 
2937 **
2938 ** Pedantic note: this routine traverses p->aFile directly, rather than
2939 ** using manifest_file_next(), so that delta manifests are rendered as-is
2940 ** instead of having their derived files. If that policy is ever changed,
2941 ** p will need to be non-const.
@@ -2991,10 +2994,13 @@
2991 KVP_STR(1, uuid, pF->zUuid);
2992 KVP_STR(1, perm, pF->zPerm);
2993 KVP_STR(1, oldName, pF->zPrior);
2994 blob_append_char(b, '}');
2995 }
 
 
 
2996 blob_append_char(b, ']');
2997 }
2998 CARD_STR2(G, p->zThreadRoot);
2999 CARD_STR2(H, p->zThreadTitle);
3000 CARD_STR2(I, p->zInReplyTo);
@@ -3020,17 +3026,22 @@
3020 blob_appendf(b, "%!j", p->azCChild[i]);
3021 }
3022 blob_append_char(b, ']');
3023 }
3024 CARD_STR2(N, p->zMimetype);
3025 if( p->nParent ){
3026 CARD_LETTER(P);
3027 blob_append_char(b, '[');
3028 for( i = 0; i < p->nParent; ++i ){
3029 if( i>0 ) blob_append_char(b, ',');
3030 blob_appendf(b, "%!j", p->azParent[i]);
 
 
3031 }
 
 
 
3032 blob_append_char(b, ']');
3033 }
3034 if( p->nCherrypick ){
3035 CARD_LETTER(Q);
3036 blob_append_char(b, '[');
@@ -3075,10 +3086,26 @@
3075 #undef CARD_STR2
3076 #undef ISA
3077 #undef KVP_STR
3078 #undef STR_OR_NULL
3079 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3080
3081 /*
3082 ** Convenience wrapper around artifact_to_json() which accepts any
3083 ** artifact name which is legal for symbolic_name_to_rid(). On success
3084 ** it returns the rid of the artifact. Returns 0 if no such artifact
@@ -3086,21 +3113,63 @@
3086 **
3087 ** pOut is not cleared before rendering, so the caller needs to do
3088 ** that if it's important for their use case.
3089 */
3090 int artifact_to_json_by_name(const char *zName, Blob *pOut){
3091 int rid;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3092
3093 rid = symbolic_name_to_rid(zName, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3094 if( rid>0 ){
3095 Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3096 assert(p && "Is it possible to fail this if rid is a phantom?");
3097 artifact_to_json(p, pOut);
3098 manifest_destroy(p);
 
 
3099 }
3100 return rid;
 
 
 
3101 }
 
 
3102
3103 /*
3104 ** COMMAND: test-artifact-to-json
3105 **
3106 ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3107
--- src/manifest.c
+++ src/manifest.c
@@ -2931,11 +2931,14 @@
2931 }
2932 return NULL;
2933 }
2934
2935 /*
2936 ** Creates a JSON representation of p, appending it to b.
2937 **
2938 ** b is not cleared before rendering, so the caller needs to do that
2939 ** if it's important for their use case.
2940 **
2941 ** Pedantic note: this routine traverses p->aFile directly, rather than
2942 ** using manifest_file_next(), so that delta manifests are rendered as-is
2943 ** instead of having their derived files. If that policy is ever changed,
2944 ** p will need to be non-const.
@@ -2991,10 +2994,13 @@
2994 KVP_STR(1, uuid, pF->zUuid);
2995 KVP_STR(1, perm, pF->zPerm);
2996 KVP_STR(1, oldName, pF->zPrior);
2997 blob_append_char(b, '}');
2998 }
2999 /* Special case: model checkins with no F-card as having an empty
3000 ** array, rather than no F-cards, to hypothetically simplify
3001 ** handling in JSON queries. */
3002 blob_append_char(b, ']');
3003 }
3004 CARD_STR2(G, p->zThreadRoot);
3005 CARD_STR2(H, p->zThreadTitle);
3006 CARD_STR2(I, p->zInReplyTo);
@@ -3020,17 +3026,22 @@
3026 blob_appendf(b, "%!j", p->azCChild[i]);
3027 }
3028 blob_append_char(b, ']');
3029 }
3030 CARD_STR2(N, p->zMimetype);
3031 ISA( CFTYPE_MANIFEST ){
3032 CARD_LETTER(P);
3033 blob_append_char(b, '[');
3034 if( p->nParent ){
3035 for( i = 0; i < p->nParent; ++i ){
3036 if( i>0 ) blob_append_char(b, ',');
3037 blob_appendf(b, "%!j", p->azParent[i]);
3038 }
3039 }
3040 /* Special case: model checkins with no P-card as having
3041 ** an empty array, rather than no P-card, to hypothetically
3042 ** simplify handling in JSON queries. */
3043 blob_append_char(b, ']');
3044 }
3045 if( p->nCherrypick ){
3046 CARD_LETTER(Q);
3047 blob_append_char(b, '[');
@@ -3075,10 +3086,26 @@
3086 #undef CARD_STR2
3087 #undef ISA
3088 #undef KVP_STR
3089 #undef STR_OR_NULL
3090 }
3091
3092 /*
3093 ** Convenience wrapper around artifact_to_json() which expects rid to
3094 ** be the blob.rid of any artifact type. If it can load a Manifest
3095 ** with that rid, it returns rid, else it returns 0.
3096 */
3097 int artifact_to_json_by_rid(int rid, Blob *pOut){
3098 Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3099 if( p ){
3100 artifact_to_json(p, pOut);
3101 manifest_destroy(p);
3102 }else{
3103 rid = 0;
3104 }
3105 return rid;
3106 }
3107
3108 /*
3109 ** Convenience wrapper around artifact_to_json() which accepts any
3110 ** artifact name which is legal for symbolic_name_to_rid(). On success
3111 ** it returns the rid of the artifact. Returns 0 if no such artifact
@@ -3086,21 +3113,63 @@
3113 **
3114 ** pOut is not cleared before rendering, so the caller needs to do
3115 ** that if it's important for their use case.
3116 */
3117 int artifact_to_json_by_name(const char *zName, Blob *pOut){
3118 const int rid = symbolic_name_to_rid(zName, 0);
3119 return rid>0
3120 ? artifact_to_json_by_rid(rid, pOut)
3121 : rid;
3122 }
3123
3124 /*
3125 ** SQLite UDF for artifact_to_json(). Its single argument should be
3126 ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3127 ** name, as per symbolic_name_to_rid(). If an artifact is found then
3128 ** the result of the UDF is that JSON as a string, else it evaluates
3129 ** to NULL.
3130 */
3131 void artifact_to_json_sql_func(
3132 sqlite3_context *context,
3133 int argc,
3134 sqlite3_value **argv
3135 ){
3136 int rid = 0;
3137 Blob b = empty_blob;
3138
3139 if(1 != argc){
3140 goto error_usage;
3141 }
3142 switch( sqlite3_value_type(argv[0]) ){
3143 case SQLITE_INTEGER:
3144 rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3145 break;
3146 case SQLITE_TEXT:{
3147 const char * z = (const char *)sqlite3_value_text(argv[0]);
3148 if( z ){
3149 rid = artifact_to_json_by_name(z, &b);
3150 }
3151 break;
3152 }
3153 default:
3154 goto error_usage;
3155 }
3156 if( rid>0 ){
3157 sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3158 SQLITE_TRANSIENT);
3159 blob_reset(&b);
3160 }else{
3161 /* We should arguably error out if rid<0 (ambiguous name) */
3162 sqlite3_result_null(context);
3163 }
3164 return;
3165 error_usage:
3166 sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3167 "artifact symbolic name", -1);
3168 }
3169
3170
3171
3172 /*
3173 ** COMMAND: test-artifact-to-json
3174 **
3175 ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3176

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button