Changeset: 2072196020bf for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=2072196020bf
Added Files:
sql/test/BugTracker-2019/Tests/next-get-value-bulk.Bug-6766.stable.err
Modified Files:
clients/Tests/MAL-signatures.stable.out
clients/Tests/MAL-signatures.stable.out.int128
sql/backends/monet5/sql.c
sql/backends/monet5/sql.h
sql/backends/monet5/sql.mal
sql/storage/store_sequence.c
sql/storage/store_sequence.h
sql/test/BugTracker-2018/Tests/negative-sequences.Bug-6665.stable.err
sql/test/BugTracker-2019/Tests/next-get-value-bulk.Bug-6766.sql
sql/test/BugTracker-2019/Tests/next-get-value-bulk.Bug-6766.stable.out
Branch: default
Log Message:
Merge with Nov2019
diffs (truncated from 736 to 300 lines):
diff --git a/clients/Tests/MAL-signatures.stable.out
b/clients/Tests/MAL-signatures.stable.out
--- a/clients/Tests/MAL-signatures.stable.out
+++ b/clients/Tests/MAL-signatures.stable.out
@@ -8973,6 +8973,9 @@ stdout of test 'MAL-signatures` in direc
[ "batsql", "diff", "pattern batsql.diff(b:bat[:any_1]):bat[:bit] ",
"SQLdiff;", "return true if cur != prev row" ]
[ "batsql", "diff", "pattern batsql.diff(p:bat[:bit],
b:bat[:any_1]):bat[:bit] ", "SQLdiff;", "return true if cur != prev row"
]
[ "batsql", "first_value", "pattern batsql.first_value(b:bat[:any_1],
s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLfirst_value;", "return
the first value of groups" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:bat[:str],
sequence:str):bat[:lng] ", "mvc_bat_get_value;", "return the current value
of the sequence" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:str,
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schema_sequences_get_value;",
"return the current value of sequences" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:bat[:str],
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schemas_sequences_get_value;",
"return the current value of sequences" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2, d:any_1,
p:any_3, o:any_4):bat[:any_1] ", "SQLlag;", "return the value in the
previous 'l' row in the partition or 'd' if non existent" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2,
d:bat[:any_1], p:any_3, o:any_4):bat[:any_1] ", "SQLlag;", "return the
value in the previous 'l' row in the partition or 'd' if non existent" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2, p:any_3,
o:any_4):bat[:any_1] ", "SQLlag;", "return the value in the previous
'l' row in the partition or NULL if non existent" ]
@@ -8991,6 +8994,8 @@ stdout of test 'MAL-signatures` in direc
[ "batsql", "max", "pattern batsql.max(b:bat[:any_1], s:bat[:lng],
e:bat[:lng]):bat[:any_1] ", "SQLmax;", "return the maximum of groups"
]
[ "batsql", "min", "pattern batsql.min(b:bat[:any_1], s:bat[:lng],
e:bat[:lng]):bat[:any_1] ", "SQLmin;", "return the minimum of groups"
]
[ "batsql", "next_value", "pattern batsql.next_value(sname:bat[:str],
sequence:str):bat[:lng] ", "mvc_bat_next_value;", "return the next value of
the sequence" ]
+[ "batsql", "next_value", "pattern batsql.next_value(sname:str,
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schema_sequences_next_value;",
"return the next value of sequences" ]
+[ "batsql", "next_value", "pattern batsql.next_value(sname:bat[:str],
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schemas_sequences_next_value;",
"return the next value of sequences" ]
[ "batsql", "nth_value", "pattern batsql.nth_value(b:bat[:any_1],
n:any_2, s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLnth_value;",
"return the nth value of each group" ]
[ "batsql", "nth_value", "pattern batsql.nth_value(b:bat[:any_1],
n:bat[:any_2], s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLnth_value;",
"return the nth value of each group" ]
[ "batsql", "ntile", "pattern batsql.ntile(b:bat[:any_1], n:any_2,
p:any_3, o:any_4):bat[:any_2] ", "SQLntile;", "return the groups divided as
equally as possible" ]
@@ -11666,11 +11671,12 @@ stdout of test 'MAL-signatures` in direc
[ "sql", "export_table", "unsafe pattern sql.export_table(fname:str,
fmt:str, colsep:str, recsep:str, qout:str, nullrep:str, onclient:int,
tbl:bat[:str], attr:bat[:str], tpe:bat[:str], len:bat[:int], scale:bat[:int],
cols:any...):int ", "mvc_export_row_wrap;", "Prepare a table result set for
the COPY INTO stream" ]
[ "sql", "export_table", "unsafe pattern sql.export_table(fname:str,
fmt:str, colsep:str, recsep:str, qout:str, nullrep:str, onclient:int,
tbl:bat[:str], attr:bat[:str], tpe:bat[:str], len:bat[:int], scale:bat[:int],
cols:bat[:any]...):int ", "mvc_export_table_wrap;", "Prepare a
table result set for the COPY INTO stream" ]
[ "sql", "first_value", "pattern sql.first_value(b:any_1, s:lng,
e:lng):any_1 ", "SQLfirst_value;", "return the first value of
groups" ]
-[ "sql", "flush_log", "command sql.flush_log():void ",
"SQLflush_log;", "flush the log now" ]
+[ "sql", "flush_log", "command sql.flush_log():void ",
"SQLflush_log;", "start flushing the write ahead log" ]
[ "sql", "getVariable", "pattern sql.getVariable(mvc:int,
varname:str):any_1 ", "getVariable;", "Get the value of a session variable" ]
[ "sql", "getVersion", "command sql.getVersion(clientid:int):lng ",
"mvc_getVersion;", "Return the database version identifier for a client."
]
[ "sql", "get_value", "pattern sql.get_value(sname:str,
sequence:str):lng ", "mvc_get_value;", "return the current value of the
sequence" ]
[ "sql", "grow", "pattern sql.grow(tid:bat[:oid], X_0:any_1):int ",
"mvc_grow_wrap;", "Resize the tid column of a declared table." ]
+[ "sql", "hot_snapshot", "command sql.hot_snapshot(tarfile:str):void ",
"SQLhot_snapshot;", "Write db snapshot to the given tar(.gz) file" ]
[ "sql", "importTable", "unsafe pattern sql.importTable(sname:str,
tname:str, onclient:int, fname:str...):bat[:any]... ",
"mvc_bin_import_table_wrap;", "Import a table from the files (fname)" ]
[ "sql", "include", "pattern sql.include(fname:str):void ",
"SQLinclude;", "Compile and execute a sql statements on the file" ]
[ "sql", "lag", "pattern sql.lag(b:any_1, l:any_2, d:any_1, p:any_3,
o:any_4):any_1 ", "SQLlag;", "return the value in the previous 'l' row in
the partition or 'd' if non existent" ]
diff --git a/clients/Tests/MAL-signatures.stable.out.int128
b/clients/Tests/MAL-signatures.stable.out.int128
--- a/clients/Tests/MAL-signatures.stable.out.int128
+++ b/clients/Tests/MAL-signatures.stable.out.int128
@@ -12695,6 +12695,9 @@ stdout of test 'MAL-signatures` in direc
[ "batsql", "diff", "pattern batsql.diff(b:bat[:any_1]):bat[:bit] ",
"SQLdiff;", "return true if cur != prev row" ]
[ "batsql", "diff", "pattern batsql.diff(p:bat[:bit],
b:bat[:any_1]):bat[:bit] ", "SQLdiff;", "return true if cur != prev row"
]
[ "batsql", "first_value", "pattern batsql.first_value(b:bat[:any_1],
s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLfirst_value;", "return
the first value of groups" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:bat[:str],
sequence:str):bat[:lng] ", "mvc_bat_get_value;", "return the current value
of the sequence" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:str,
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schema_sequences_get_value;",
"return the current value of sequences" ]
+[ "batsql", "get_value", "pattern batsql.get_value(sname:bat[:str],
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schemas_sequences_get_value;",
"return the current value of sequences" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2, d:any_1,
p:any_3, o:any_4):bat[:any_1] ", "SQLlag;", "return the value in the
previous 'l' row in the partition or 'd' if non existent" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2,
d:bat[:any_1], p:any_3, o:any_4):bat[:any_1] ", "SQLlag;", "return the
value in the previous 'l' row in the partition or 'd' if non existent" ]
[ "batsql", "lag", "pattern batsql.lag(b:bat[:any_1], l:any_2, p:any_3,
o:any_4):bat[:any_1] ", "SQLlag;", "return the value in the previous
'l' row in the partition or NULL if non existent" ]
@@ -12713,6 +12716,8 @@ stdout of test 'MAL-signatures` in direc
[ "batsql", "max", "pattern batsql.max(b:bat[:any_1], s:bat[:lng],
e:bat[:lng]):bat[:any_1] ", "SQLmax;", "return the maximum of groups"
]
[ "batsql", "min", "pattern batsql.min(b:bat[:any_1], s:bat[:lng],
e:bat[:lng]):bat[:any_1] ", "SQLmin;", "return the minimum of groups"
]
[ "batsql", "next_value", "pattern batsql.next_value(sname:bat[:str],
sequence:str):bat[:lng] ", "mvc_bat_next_value;", "return the next value of
the sequence" ]
+[ "batsql", "next_value", "pattern batsql.next_value(sname:str,
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schema_sequences_next_value;",
"return the next value of sequences" ]
+[ "batsql", "next_value", "pattern batsql.next_value(sname:bat[:str],
sequence:bat[:str]):bat[:lng] ", "mvc_bat_schemas_sequences_next_value;",
"return the next value of sequences" ]
[ "batsql", "nth_value", "pattern batsql.nth_value(b:bat[:any_1],
n:any_2, s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLnth_value;",
"return the nth value of each group" ]
[ "batsql", "nth_value", "pattern batsql.nth_value(b:bat[:any_1],
n:bat[:any_2], s:bat[:lng], e:bat[:lng]):bat[:any_1] ", "SQLnth_value;",
"return the nth value of each group" ]
[ "batsql", "ntile", "pattern batsql.ntile(b:bat[:any_1], n:any_2,
p:any_3, o:any_4):bat[:any_2] ", "SQLntile;", "return the groups divided as
equally as possible" ]
@@ -16072,11 +16077,12 @@ stdout of test 'MAL-signatures` in direc
[ "sql", "export_table", "unsafe pattern sql.export_table(fname:str,
fmt:str, colsep:str, recsep:str, qout:str, nullrep:str, onclient:int,
tbl:bat[:str], attr:bat[:str], tpe:bat[:str], len:bat[:int], scale:bat[:int],
cols:any...):int ", "mvc_export_row_wrap;", "Prepare a table result set for
the COPY INTO stream" ]
[ "sql", "export_table", "unsafe pattern sql.export_table(fname:str,
fmt:str, colsep:str, recsep:str, qout:str, nullrep:str, onclient:int,
tbl:bat[:str], attr:bat[:str], tpe:bat[:str], len:bat[:int], scale:bat[:int],
cols:bat[:any]...):int ", "mvc_export_table_wrap;", "Prepare a
table result set for the COPY INTO stream" ]
[ "sql", "first_value", "pattern sql.first_value(b:any_1, s:lng,
e:lng):any_1 ", "SQLfirst_value;", "return the first value of
groups" ]
-[ "sql", "flush_log", "command sql.flush_log():void ",
"SQLflush_log;", "flush the log now" ]
+[ "sql", "flush_log", "command sql.flush_log():void ",
"SQLflush_log;", "start flushing the write ahead log" ]
[ "sql", "getVariable", "pattern sql.getVariable(mvc:int,
varname:str):any_1 ", "getVariable;", "Get the value of a session variable" ]
[ "sql", "getVersion", "command sql.getVersion(clientid:int):lng ",
"mvc_getVersion;", "Return the database version identifier for a client."
]
[ "sql", "get_value", "pattern sql.get_value(sname:str,
sequence:str):lng ", "mvc_get_value;", "return the current value of the
sequence" ]
[ "sql", "grow", "pattern sql.grow(tid:bat[:oid], X_0:any_1):int ",
"mvc_grow_wrap;", "Resize the tid column of a declared table." ]
+[ "sql", "hot_snapshot", "command sql.hot_snapshot(tarfile:str):void ",
"SQLhot_snapshot;", "Write db snapshot to the given tar(.gz) file" ]
[ "sql", "importTable", "unsafe pattern sql.importTable(sname:str,
tname:str, onclient:int, fname:str...):bat[:any]... ",
"mvc_bin_import_table_wrap;", "Import a table from the files (fname)" ]
[ "sql", "include", "pattern sql.include(fname:str):void ",
"SQLinclude;", "Compile and execute a sql statements on the file" ]
[ "sql", "lag", "pattern sql.lag(b:any_1, l:any_2, d:any_1, p:any_3,
o:any_4):any_1 ", "SQLlag;", "return the value in the previous 'l' row in
the partition or 'd' if non existent" ]
diff --git a/sql/backends/monet5/sql.c b/sql/backends/monet5/sql.c
--- a/sql/backends/monet5/sql.c
+++ b/sql/backends/monet5/sql.c
@@ -754,6 +754,7 @@ mvc_next_value(Client cntxt, MalBlkPtr m
mvc *m = NULL;
str msg;
sql_schema *s;
+ sql_sequence *seq;
lng *res = getArgReference_lng(stk, pci, 0);
const char *sname = *getArgReference_str(stk, pci, 1);
const char *seqname = *getArgReference_str(stk, pci, 2);
@@ -762,26 +763,51 @@ mvc_next_value(Client cntxt, MalBlkPtr m
return msg;
if ((msg = checkSQLContext(cntxt)) != NULL)
return msg;
- s = mvc_bind_schema(m, sname);
- if (s) {
- sql_sequence *seq = find_sql_sequence(s, seqname);
-
- if (seq && seq_next_value(seq, res)) {
- m->last_id = *res;
- stack_set_number(m, "last_id", m->last_id);
- return MAL_SUCCEED;
- }
- }
- throw(SQL, "sql.next_value", SQLSTATE(42000) "Error in fetching next
value");
+ if (!(s = mvc_bind_schema(m, sname)))
+ throw(SQL, "sql.next_value", SQLSTATE(3F000) "Cannot find the
schema %s", sname);
+ if (!(seq = find_sql_sequence(s, seqname)))
+ throw(SQL, "sql.next_value", SQLSTATE(HY050) "Failed to fetch
sequence %s.%s", sname, seqname);
+
+ if (seq_next_value(seq, res)) {
+ m->last_id = *res;
+ stack_set_number(m, "last_id", m->last_id);
+ return MAL_SUCCEED;
+ }
+ throw(SQL, "sql.next_value", SQLSTATE(42000) "Error in fetching next
value for sequence %s.%s", sname, seqname);
}
-/* str mvc_bat_next_value(bat *res, int *sid, str *seqname); */
+/* str mvc_get_value(lng *res, str *sname, str *seqname); */
str
-mvc_bat_next_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
+mvc_get_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
{
mvc *m = NULL;
str msg;
- BAT *b, *r;
+ sql_schema *s;
+ sql_sequence *seq;
+ lng *res = getArgReference_lng(stk, pci, 0);
+ const char *sname = *getArgReference_str(stk, pci, 1);
+ const char *seqname = *getArgReference_str(stk, pci, 2);
+
+ if ((msg = getSQLContext(cntxt, mb, &m, NULL)) != NULL)
+ return msg;
+ if ((msg = checkSQLContext(cntxt)) != NULL)
+ return msg;
+ if (!(s = mvc_bind_schema(m, sname)))
+ throw(SQL, "sql.get_value", SQLSTATE(3F000) "Cannot find the
schema %s", sname);
+ if (!(seq = find_sql_sequence(s, seqname)))
+ throw(SQL, "sql.get_value", SQLSTATE(HY050) "Failed to fetch
sequence %s.%s", sname, seqname);
+
+ if (seq_get_value(seq, res))
+ return MAL_SUCCEED;
+ throw(SQL, "sql.get_value", SQLSTATE(42000) "Error in fetching current
value for sequence %s.%s", sname, seqname);
+}
+
+static str
+mvc_bat_sequence_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr
pci, int (*bulk_func)(seqbulk *, lng *), const char* call)
+{
+ mvc *m = NULL;
+ str msg = MAL_SUCCEED;
+ BAT *b = NULL, *r = NULL;
BUN p, q;
sql_schema *s = NULL;
sql_sequence *seq = NULL;
@@ -797,20 +823,15 @@ mvc_bat_next_value(Client cntxt, MalBlkP
return msg;
if ((b = BATdescriptor(sid)) == NULL)
- throw(SQL, "sql.next_value", SQLSTATE(HY005) "Cannot access
column descriptor");
-
- r = COLnew(b->hseqbase, TYPE_lng, BATcount(b), TRANSIENT);
- if (!r) {
- BBPunfix(b->batCacheid);
- throw(SQL, "sql.next_value", SQLSTATE(HY001) MAL_MALLOC_FAIL);
- }
-
- if (!BATcount(b)) {
- BBPunfix(b->batCacheid);
- BBPkeepref(r->batCacheid);
- *res = r->batCacheid;
- return MAL_SUCCEED;
- }
+ throw(SQL, call, SQLSTATE(HY005) "Cannot access column
descriptor");
+
+ if (!(r = COLnew(b->hseqbase, TYPE_lng, BATcount(b), TRANSIENT))) {
+ msg = createException(SQL, call, SQLSTATE(HY001)
MAL_MALLOC_FAIL);
+ goto bailout;
+ }
+
+ if (!BATcount(b))
+ goto bailout; /* Success case */
bi = bat_iterator(b);
BATloop(b, p, q) {
@@ -818,60 +839,222 @@ mvc_bat_next_value(Client cntxt, MalBlkP
lng l;
if (!s || strcmp(s->base.name, sname) != 0) {
- if (sb)
+ if (sb) {
seqbulk_destroy(sb);
+ sb = NULL;
+ }
s = mvc_bind_schema(m, sname);
seq = NULL;
- if (!s || (seq = find_sql_sequence(s, seqname)) == NULL
|| !(sb = seqbulk_create(seq, BATcount(b)))) {
- BBPunfix(b->batCacheid);
- BBPunfix(r->batCacheid);
- throw(SQL, "sql.next_value", SQLSTATE(HY050)
"Cannot find the sequence %s.%s", sname,seqname);
+ if (!s || !(seq = find_sql_sequence(s, seqname)) ||
!(sb = seqbulk_create(seq, BATcount(b)))) {
+ msg = createException(SQL, call,
SQLSTATE(HY050) "Cannot find the sequence %s.%s", sname, seqname);
+ goto bailout;
}
}
- if (!seqbulk_next_value(sb, &l)) {
- BBPunfix(b->batCacheid);
- BBPunfix(r->batCacheid);
- seqbulk_destroy(sb);
- throw(SQL, "sql.next_value", SQLSTATE(HY050) "Cannot
generate next sequence value %s.%s", sname, seqname);
+ if (!bulk_func(sb, &l)) {
+ msg = createException(SQL, call, SQLSTATE(HY050)
"Cannot generate next sequence value %s.%s", sname, seqname);
+ goto bailout;
}
if (BUNappend(r, &l, false) != GDK_SUCCEED) {
- BBPunfix(b->batCacheid);
- BBPunfix(r->batCacheid);
- seqbulk_destroy(sb);
- throw(SQL, "sql.next_value", SQLSTATE(HY001)
MAL_MALLOC_FAIL);
+ msg = createException(SQL, call, SQLSTATE(HY001)
MAL_MALLOC_FAIL);
+ goto bailout;
}
}
+
+bailout:
if (sb)
seqbulk_destroy(sb);
- BBPunfix(b->batCacheid);
- BBPkeepref(r->batCacheid);
- *res = r->batCacheid;
- return MAL_SUCCEED;
+ if (b)
+ BBPunfix(b->batCacheid);
+ if (msg)
+ BBPreclaim(r);
+ else
+ BBPkeepref(*res = r->batCacheid);
+ return msg;
}
-/* str mvc_get_value(lng *res, str *sname, str *seqname); */
+str
+mvc_bat_next_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
+{
+ return mvc_bat_sequence_value(cntxt, mb, stk, pci, seqbulk_next_value,
"sql.next_value");
+}
+
str
-mvc_get_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
+mvc_bat_get_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
+{
+ return mvc_bat_sequence_value(cntxt, mb, stk, pci, seqbulk_get_value,
"sql.get_value");
+}
+
+static str
+mvc_bat_schema_sequences_value(Client cntxt, MalBlkPtr mb, MalStkPtr stk,
InstrPtr pci, int (*bulk_func)(seqbulk *, lng *), const char* call)
{
mvc *m = NULL;
- str msg;
- sql_schema *s;
- lng *res = getArgReference_lng(stk, pci, 0);
+ str msg = MAL_SUCCEED;
+ BAT *b = NULL, *r = NULL;
+ BUN p, q;
+ sql_schema *s = NULL;
+ sql_sequence *seq = NULL;
+ seqbulk *sb = NULL;
+ BATiter bi;
+ bat *res = getArgReference_bat(stk, pci, 0);
const char *sname = *getArgReference_str(stk, pci, 1);
- const char *seqname = *getArgReference_str(stk, pci, 2);
+ bat sid = *getArgReference_bat(stk, pci, 2);
if ((msg = getSQLContext(cntxt, mb, &m, NULL)) != NULL)
return msg;
if ((msg = checkSQLContext(cntxt)) != NULL)
return msg;
- s = mvc_bind_schema(m, sname);
- if (s) {
- sql_sequence *seq = find_sql_sequence(s, seqname);
-
- if (seq && seq_get_value(seq, res))
- return MAL_SUCCEED;
- }
- throw(SQL, "sql.get_value", SQLSTATE(HY050) "Failed to fetch sequence
%s.%s", sname, seqname);
+
+ if ((b = BATdescriptor(sid)) == NULL)
+ throw(SQL, call, SQLSTATE(HY005) "Cannot access column
descriptor");
+
+ if (!(r = COLnew(b->hseqbase, TYPE_lng, BATcount(b), TRANSIENT))) {
+ msg = createException(SQL, call, SQLSTATE(HY001)
MAL_MALLOC_FAIL);
+ goto bailout;
+ }
+
+ if (!(s = mvc_bind_schema(m, sname))) {
+ msg = createException(SQL, call, SQLSTATE(3F000) "Cannot find
the schema %s", sname);
+ goto bailout;
+ }
+
+ if (!BATcount(b))
+ goto bailout; /* Success case */
+
+ bi = bat_iterator(b);
+ BATloop(b, p, q) {
+ str seqname = BUNtvar(bi, p);
+ lng l;
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list