@@ -1919,6 +1919,209 @@ fam_get_all(FAMObject *self, PyObject *key) {
19191919# undef GET_ALL_FLEXIBLE
19201920
19211921
1922+ // Fill variants of the GET_ALL_* macros: on a miss, write -1 into the output slot and
1923+ // continue (instead of raising KeyError). Depend on self, key_size, key_array, i, b, array.
1924+ # define GET_ALL_FILL_SCALARS (npy_type_src , npy_type_dst , kat , lookup_func , hash_func , post_deref ) \
1925+ { \
1926+ npy_type_dst v; \
1927+ Py_ssize_t table_pos; \
1928+ for (; i < key_size; i++) { \
1929+ v = post_deref(*(npy_type_src*)PyArray_GETPTR1(key_array, i)); \
1930+ table_pos = lookup_func(self, v, hash_func(v), kat); \
1931+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1932+ if (PyErr_Occurred()) { \
1933+ Py_DECREF(array); \
1934+ return NULL; \
1935+ } \
1936+ b[i] = -1; \
1937+ continue; \
1938+ } \
1939+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1940+ } \
1941+ } \
1942+
1943+ # define GET_ALL_FILL_DT64 (npy_type_src , npy_type_dst , kat , lookup_func , hash_func ) \
1944+ { \
1945+ npy_type_dst v; \
1946+ Py_ssize_t table_pos; \
1947+ for (; i < key_size; i++) { \
1948+ v = *(npy_type_src*)PyArray_GETPTR1(key_array, i); \
1949+ table_pos = lookup_func(self, v, hash_func(v), kat); \
1950+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1951+ if (PyErr_Occurred()) { \
1952+ Py_DECREF(array); \
1953+ return NULL; \
1954+ } \
1955+ b[i] = -1; \
1956+ continue; \
1957+ } \
1958+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1959+ } \
1960+ } \
1961+
1962+ # define GET_ALL_FILL_FLEXIBLE (char_type , get_end_func , lookup_func , hash_func ) \
1963+ { \
1964+ char_type* v; \
1965+ Py_ssize_t dt_size = PyArray_ITEMSIZE(key_array) / sizeof(char_type); \
1966+ Py_ssize_t k_size; \
1967+ Py_ssize_t table_pos; \
1968+ for (; i < key_size; i++) { \
1969+ v = (char_type*)PyArray_GETPTR1(key_array, i); \
1970+ k_size = get_end_func(v, dt_size) - v; \
1971+ table_pos = lookup_func(self, v, k_size, hash_func(v, k_size)); \
1972+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1973+ if (PyErr_Occurred()) { \
1974+ Py_DECREF(array); \
1975+ return NULL; \
1976+ } \
1977+ b[i] = -1; \
1978+ continue; \
1979+ } \
1980+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1981+ } \
1982+ } \
1983+
1984+ // Given a list or array of keys, return an input-aligned int64 array of looked-up
1985+ // positions, with -1 for any key not found (never raises KeyError). This is the
1986+ // "vectorized get with default -1" needed by left/outer joins. Immutable output.
1987+ static PyObject *
1988+ fam_get_all_fill (FAMObject * self , PyObject * key ) {
1989+ Py_ssize_t key_size = 0 ;
1990+ Py_ssize_t keys_pos = -1 ;
1991+ PyObject * k = NULL ;
1992+ PyObject * array = NULL ;
1993+ Py_ssize_t i = 0 ;
1994+
1995+ int key_is_list ;
1996+ if (PyList_CheckExact (key )) {
1997+ key_is_list = 1 ;
1998+ key_size = PyList_GET_SIZE (key );
1999+ }
2000+ else if (PyArray_Check (key )) {
2001+ key_is_list = 0 ;
2002+ key_size = PyArray_SIZE ((PyArrayObject * )key );
2003+ }
2004+ else {
2005+ PyErr_SetString (PyExc_TypeError , "Must provide a list or array." );
2006+ return NULL ;
2007+ }
2008+
2009+ npy_intp dims [] = {key_size };
2010+ array = PyArray_EMPTY (1 , dims , NPY_INT64 , 0 );
2011+ if (array == NULL ) {
2012+ return NULL ;
2013+ }
2014+ npy_int64 * b = (npy_int64 * )PyArray_DATA ((PyArrayObject * )array );
2015+
2016+ if (key_is_list ) {
2017+ for (; i < key_size ; i ++ ) {
2018+ k = PyList_GET_ITEM (key , i ); // borrow
2019+ keys_pos = lookup (self , k );
2020+ if (keys_pos < 0 ) {
2021+ if (PyErr_Occurred ()) {
2022+ Py_DECREF (array );
2023+ return NULL ;
2024+ }
2025+ b [i ] = -1 ;
2026+ continue ;
2027+ }
2028+ b [i ] = (npy_int64 )keys_pos ;
2029+ }
2030+ }
2031+ else { // key is an array
2032+ PyArrayObject * key_array = (PyArrayObject * )key ;
2033+ int key_array_t = PyArray_TYPE (key_array );
2034+ int use_typed = kat_is_kind (self -> keys_array_type , PyArray_DESCR (key_array )-> kind );
2035+ if (use_typed && key_array_t == NPY_DATETIME ) {
2036+ NPY_DATETIMEUNIT key_unit = AK_dt_unit_from_array (key_array );
2037+ if (!kat_is_datetime_unit (self -> keys_array_type , key_unit )) {
2038+ // mismatched units: use the coercing scalar path (below) rather than
2039+ // raising, so equal instants still resolve (and true misses give -1)
2040+ use_typed = 0 ;
2041+ }
2042+ }
2043+ if (use_typed ) {
2044+ switch (key_array_t ) {
2045+ case NPY_INT64 :
2046+ GET_ALL_FILL_SCALARS (npy_int64 , npy_int64 , KAT_INT64 , lookup_hash_int , int_to_hash , );
2047+ break ;
2048+ case NPY_INT32 :
2049+ GET_ALL_FILL_SCALARS (npy_int32 , npy_int64 , KAT_INT32 , lookup_hash_int , int_to_hash , );
2050+ break ;
2051+ case NPY_INT16 :
2052+ GET_ALL_FILL_SCALARS (npy_int16 , npy_int64 , KAT_INT16 , lookup_hash_int , int_to_hash , );
2053+ break ;
2054+ case NPY_INT8 :
2055+ GET_ALL_FILL_SCALARS (npy_int8 , npy_int64 , KAT_INT8 , lookup_hash_int , int_to_hash , );
2056+ break ;
2057+ case NPY_UINT64 :
2058+ GET_ALL_FILL_SCALARS (npy_uint64 , npy_uint64 , KAT_UINT64 , lookup_hash_uint , uint_to_hash , );
2059+ break ;
2060+ case NPY_UINT32 :
2061+ GET_ALL_FILL_SCALARS (npy_uint32 , npy_uint64 , KAT_UINT32 , lookup_hash_uint , uint_to_hash , );
2062+ break ;
2063+ case NPY_UINT16 :
2064+ GET_ALL_FILL_SCALARS (npy_uint16 , npy_uint64 , KAT_UINT16 , lookup_hash_uint , uint_to_hash , );
2065+ break ;
2066+ case NPY_UINT8 :
2067+ GET_ALL_FILL_SCALARS (npy_uint8 , npy_uint64 , KAT_UINT8 , lookup_hash_uint , uint_to_hash , );
2068+ break ;
2069+ case NPY_FLOAT64 :
2070+ GET_ALL_FILL_SCALARS (npy_double , npy_double , KAT_FLOAT64 , lookup_hash_double , double_to_hash , );
2071+ break ;
2072+ case NPY_FLOAT32 :
2073+ GET_ALL_FILL_SCALARS (npy_float , npy_double , KAT_FLOAT32 , lookup_hash_double , double_to_hash , );
2074+ break ;
2075+ case NPY_FLOAT16 :
2076+ GET_ALL_FILL_SCALARS (npy_half , npy_double , KAT_FLOAT16 , lookup_hash_double , double_to_hash , npy_half_to_double );
2077+ break ;
2078+ case NPY_UNICODE :
2079+ GET_ALL_FILL_FLEXIBLE (Py_UCS4 , ucs4_get_end_p , lookup_hash_unicode , unicode_to_hash );
2080+ break ;
2081+ case NPY_STRING :
2082+ GET_ALL_FILL_FLEXIBLE (char , char_get_end_p , lookup_hash_string , string_to_hash );
2083+ break ;
2084+ case NPY_DATETIME :
2085+ GET_ALL_FILL_DT64 (npy_int64 , npy_int64 , KAT_INT64 , lookup_hash_int , int_to_hash );
2086+ break ;
2087+ default :
2088+ use_typed = 0 ; // unhandled kind: fall to the scalar path below
2089+ break ;
2090+ }
2091+ }
2092+ if (!use_typed ) {
2093+ for (; i < key_size ; i ++ ) {
2094+ k = PyArray_ToScalar (PyArray_GETPTR1 (key_array , i ), key_array );
2095+ if (k == NULL ) {
2096+ Py_DECREF (array );
2097+ return NULL ;
2098+ }
2099+ keys_pos = lookup (self , k );
2100+ if (keys_pos < 0 ) {
2101+ if (PyErr_Occurred ()) {
2102+ Py_DECREF (k );
2103+ Py_DECREF (array );
2104+ return NULL ;
2105+ }
2106+ Py_DECREF (k );
2107+ b [i ] = -1 ;
2108+ continue ;
2109+ }
2110+ Py_DECREF (k );
2111+ b [i ] = (npy_int64 )keys_pos ;
2112+ }
2113+ }
2114+ }
2115+
2116+ PyArray_CLEARFLAGS ((PyArrayObject * )array , NPY_ARRAY_WRITEABLE );
2117+ return array ;
2118+ }
2119+
2120+ # undef GET_ALL_FILL_SCALARS
2121+ # undef GET_ALL_FILL_DT64
2122+ # undef GET_ALL_FILL_FLEXIBLE
2123+
2124+
19222125static inline int
19232126append_ssize_t (
19242127 PyObject * list ,
@@ -2974,6 +3177,7 @@ static PyMethodDef fam_methods[] = {
29743177 {"keys" , (PyCFunction ) fam_keys , METH_NOARGS , NULL },
29753178 {"values" , (PyCFunction ) fam_values , METH_NOARGS , NULL },
29763179 {"get_all" , (PyCFunction ) fam_get_all , METH_O , NULL },
3180+ {"get_all_fill" , (PyCFunction ) fam_get_all_fill , METH_O , NULL },
29773181 {"get_any" , (PyCFunction ) fam_get_any , METH_O , NULL },
29783182 {NULL },
29793183};
0 commit comments