diff --git a/docs/web/management/function.mdx b/docs/web/management/function.mdx index b7fbb504b..e110612ea 100644 --- a/docs/web/management/function.mdx +++ b/docs/web/management/function.mdx @@ -564,13 +564,15 @@ itself is: as source, snapshotted at registration. ## Drop it {#drop} -`db.drop_function(name, version=...)` drops the function's name and the object -it is bound to; `version` must be a version that exists. It returns `False` if -the name was already gone. Deleting the object's content can finish after the -call returns, as a [background job](/maintenance/background-jobs). +`db.drop_function(name, version=...)` removes only that published revision from +lookup and listing, preserving other revisions and their grants. It returns +`False` for a repeated drop or a name that is already gone. A revision never +published under an existing name is an error. The last visible revision's drop +releases the name and schedules object cleanup, which can finish after the call +returns as a [background job](/maintenance/background-jobs). `drop_function_async` returns `(dropped, job)`, where `job` is a query-backed -handle. Waiting confirms catalog -removal; physical cleanup can continue afterward. +handle. Waiting confirms the revision's catalog removal; physical cleanup can +continue afterward. A deployment refuses the drop unless it also allows changes to a function's lifecycle, which is a separate setting, off by default even where Functions are diff --git a/docs/web/sql/ddl/function.mdx b/docs/web/sql/ddl/function.mdx index 854b66005..b0d14c990 100644 --- a/docs/web/sql/ddl/function.mdx +++ b/docs/web/sql/ddl/function.mdx @@ -107,12 +107,15 @@ from it. ```sql SHOW FUNCTIONS -SHOW FUNCTIONS IN "analytics" WITH (name = 'embed', limit = 100) -SHOW FUNCTIONS WITH (after_name = 'embed', after_version = '1', limit = 100) +SHOW FUNCTIONS HISTORY IN "analytics" WITH (name = 'embed', limit = 100) +SHOW FUNCTIONS HISTORY WITH (after_name = 'embed', after_version = '1', limit = 100) ``` -Rows are ordered lexicographically by name and version. `after_name` and -`after_version` must be supplied together for keyset pagination. `limit` bounds +`SHOW FUNCTIONS` returns the highest published numeric revision for each name. +Add `HISTORY` to list every published revision, as Python's `list_functions()` +does. Rows are ordered by name and numeric revision, so `2` comes before `10`. +`after_name` and `after_version` must be supplied together for keyset pagination. +`limit` bounds the response; `include_definition = false` omits the optional definition JSON. ## DROP FUNCTION {#drop} @@ -122,9 +125,12 @@ DROP FUNCTION embed WITH (version = '1') DROP FUNCTION IF EXISTS embed WITH (version = '1') ``` -`version` must identify an existing revision. DROP releases the function name -and its object binding; physical cleanup continues separately. The result -reports `dropped` or `not_found`. `IF EXISTS` allows a missing name. +DROP removes only the requested revision from lookup and listing. Other +published revisions and their grants remain available. Dropping the last visible +revision releases the name and object binding; physical cleanup continues +separately. The result reports `dropped` or `not_found`. `IF EXISTS` allows a +missing name or a repeated drop of a previously published revision. A revision +that was never published under an existing name is an error. All function object commands retain shared results even when general query-result copying is disabled, so asynchronous polling can resume on another query node. diff --git a/python/python/lancedb/db.py b/python/python/lancedb/db.py index 1c9d46558..e41a79cd5 100644 --- a/python/python/lancedb/db.py +++ b/python/python/lancedb/db.py @@ -1018,7 +1018,9 @@ class DBConnection(EnforceOverrides): """List every published immutable Function version in a namespace. Functions in child namespaces are not included. Results are ordered by - Function name then version. LanceDB Embedded connections raise + Function name then numeric version. Remote connections use + ``SHOW FUNCTIONS HISTORY`` to include every revision. + LanceDB Embedded connections raise ``NotImplementedError``. Examples @@ -1044,13 +1046,15 @@ class DBConnection(EnforceOverrides): version: str, namespace_path: Optional[List[str]] = None, ) -> bool: - """Drop a Function name and the object it was bound to. + """Drop one published Function revision, preserving its siblings. - The requested version must exist in the currently named object. Returns - True when the catalog binding was removed and False when it was absent. - Physical cleanup continues in the background; use - :meth:`drop_function_async` to access the query job. Local connections - raise NotImplementedError. + Returns True when the revision was removed and False for a repeated drop + or a missing name. A revision never published under an existing name is + an error. Dropping the last visible revision releases the name and + schedules physical cleanup in the background. Use + :meth:`drop_function_async` to + access the query job, whose completion confirms catalog removal of the + revision. Local connections raise NotImplementedError. """ raise NotImplementedError( "Function catalog operations are not supported for this connection type" @@ -1063,11 +1067,13 @@ class DBConnection(EnforceOverrides): version: str, namespace_path: Optional[List[str]] = None, ) -> Tuple[bool, Job]: - """Drop a Function binding and return its SQL query job. + """Drop one Function revision and return its SQL query job. - The boolean reports catalog removal. The job has type ``query`` and its - ID is the SQL query UUID. Repeated waits confirm that completion; - physical cleanup continues in the background. LanceDB Embedded + The boolean reports removal of the requested revision from the catalog; + sibling revisions and their grants remain available. The job has type + ``query`` and its ID is the SQL query UUID. Repeated waits confirm that + revision's removal. Only the last visible revision's drop releases the + name and schedules background object cleanup. LanceDB Embedded connections raise NotImplementedError. """ raise NotImplementedError( @@ -3515,7 +3521,13 @@ class AsyncConnection(object): version: str, namespace_path: Optional[List[str]] = None, ) -> bool: - """Drop a Function name and the object it was bound to.""" + """Drop one published Function revision, preserving its siblings. + + Returns True when the requested revision was removed and False for a + repeated drop or a missing name. A revision never published under an + existing name is an error. Only the last visible revision's removal + releases the name and schedules background object cleanup. + """ return await self._inner.drop_function(name, version, namespace_path) async def drop_function_async( @@ -3525,11 +3537,13 @@ class AsyncConnection(object): version: str, namespace_path: Optional[List[str]] = None, ) -> Tuple[bool, AsyncJob]: - """Drop a Function binding and return its SQL query job. + """Drop one Function revision and return its SQL query job. - The boolean reports catalog removal. The job has type ``query`` and its - ID is the SQL query UUID. Repeated waits confirm that completion; - physical cleanup continues in the background. + The boolean reports removal of the requested revision from the catalog; + sibling revisions and their grants remain available. The job has type + ``query`` and its ID is the SQL query UUID. Repeated waits confirm that + revision's removal. Only the last visible revision's drop releases the + name and schedules background object cleanup. """ dropped, job = await self._inner.drop_function_async( name, version, namespace_path diff --git a/rust/lancedb/src/connection.rs b/rust/lancedb/src/connection.rs index cea56a340..c0c1890b9 100644 --- a/rust/lancedb/src/connection.rs +++ b/rust/lancedb/src/connection.rs @@ -684,8 +684,8 @@ impl Connection { /// List every published immutable Function version in `namespace_path` of /// the remote catalog. Functions in child namespaces are not included. /// - /// Results are ordered by Function name then version. LanceDB Embedded databases return - /// [`Error::NotSupported`]. + /// Results are ordered by Function name then numeric version, using + /// `SHOW FUNCTIONS HISTORY`. LanceDB Embedded databases return [`Error::NotSupported`]. /// /// # Example /// @@ -719,11 +719,13 @@ impl Connection { }) } - /// Remove the current Function name binding, retaining the object history. + /// Remove one published Function revision, preserving sibling revisions and their grants. /// - /// Returns `true` when the server appended a Dropped transition and - /// `false` for an idempotent replay. LanceDB Embedded databases return - /// [`Error::NotSupported`]. + /// Returns `true` when the requested revision was removed and `false` for + /// a repeated drop or a missing name. A revision never published under an + /// existing name is an error. Only the last visible revision's removal + /// releases the name and schedules object cleanup. LanceDB Embedded databases + /// return [`Error::NotSupported`]. pub async fn drop_function( &self, name: impl AsRef, @@ -735,11 +737,13 @@ impl Connection { .await } - /// Drop a Function binding and return its SQL query handle. + /// Drop one Function revision and return its SQL query handle. /// - /// The boolean reports catalog removal. The query job retains its query ID, - /// and repeated waits confirm completion. Physical cleanup continues in the - /// background. Local databases return [`Error::NotSupported`]. + /// The boolean reports removal of the requested revision from the catalog. + /// Sibling revisions and their grants remain available. The query job retains + /// its query ID, and repeated waits confirm that revision's removal. Dropping + /// the last visible revision releases the name and schedules background object + /// cleanup. Local databases return [`Error::NotSupported`]. pub async fn drop_function_async( &self, name: impl AsRef, diff --git a/rust/lancedb/src/remote/sql/function.rs b/rust/lancedb/src/remote/sql/function.rs index 7e468f14b..671f3e3a4 100644 --- a/rust/lancedb/src/remote/sql/function.rs +++ b/rust/lancedb/src/remote/sql/function.rs @@ -108,7 +108,7 @@ impl SqlClient { let mut items: Vec = self .object_job( ExecuteQueryRequest { - query: format!("SHOW FUNCTIONS WITH ({})", sql_options.join(", ")), + query: format!("SHOW FUNCTIONS HISTORY WITH ({})", sql_options.join(", ")), default_namespace_path: namespace.to_vec(), parameters: None, }, @@ -126,10 +126,6 @@ impl SqlClient { .await? .wait() .await?; - items.sort_by(|a, b| (a.name(), a.version()).cmp(&(b.name(), b.version()))); - if let Some((name, version)) = after { - items.retain(|item| (item.name(), item.version()) > (name.as_str(), version.as_str())); - } let page_token = if items.len() > limit as usize { items.truncate(limit as usize); let last = items.last().unwrap(); diff --git a/rust/lancedb/src/remote/sql_test.rs b/rust/lancedb/src/remote/sql_test.rs index bca666356..02efc96ab 100644 --- a/rust/lancedb/src/remote/sql_test.rs +++ b/rust/lancedb/src/remote/sql_test.rs @@ -2637,9 +2637,16 @@ async fn function_listing_uses_bounded_sql_and_scoped_version_cursors() { let mut second: serde_json::Value = serde_json::from_str(VERSION).unwrap(); second["version"] = serde_json::json!("2"); let second = second.to_string(); + let mut tenth: serde_json::Value = serde_json::from_str(VERSION).unwrap(); + tenth["version"] = serde_json::json!("10"); + let tenth = tenth.to_string(); let result = RecordBatch::try_from_iter(vec![( "definition", - Arc::new(StringArray::from(vec![VERSION, second.as_str()])) as ArrayRef, + Arc::new(StringArray::from(vec![ + VERSION, + second.as_str(), + tenth.as_str(), + ])) as ArrayRef, )]) .unwrap(); let service = TestSqlService { @@ -2648,6 +2655,10 @@ async fn function_listing_uses_bounded_sql_and_scoped_version_cursors() { result, ..Default::default() }; + service.result_overrides.lock().unwrap().insert( + "SHOW FUNCTIONS HISTORY WITH (limit = 3, include_definition = true, after_name = 'embed', after_version = '2')".into(), + RecordBatch::try_from_iter(vec![("definition", Arc::new(StringArray::from(vec![tenth.as_str()])) as ArrayRef)]).unwrap(), + ); let statements = service.statements.clone(); let (client, shutdown) = start_sql_service(service, ClientConfig::default()).await; let db = RemoteDatabase::new_mock(|_| -> http::Response { @@ -2657,29 +2668,36 @@ async fn function_listing_uses_bounded_sql_and_scoped_version_cursors() { let first = db .list_functions( &["team".into()], - crate::listing::ListingOptions::default().page_limit(1), + crate::listing::ListingOptions::default().page_limit(2), ) .await .unwrap(); - assert_eq!(first.items.len(), 1); + assert_eq!( + first + .items + .iter() + .map(|item| item.version()) + .collect::>(), + ["1", "2"] + ); let token = first.page_token.unwrap(); let second = db .list_functions( &["team".into()], crate::listing::ListingOptions::default() - .page_limit(1) + .page_limit(2) .page_token(token.clone()), ) .await .unwrap(); assert_eq!(second.items.len(), 1); - assert_eq!(second.items[0].version(), "2"); + assert_eq!(second.items[0].version(), "10"); assert!(second.page_token.is_none()); assert!( db.list_functions( &["other".into()], crate::listing::ListingOptions::default() - .page_limit(1) + .page_limit(2) .page_token(token) ) .await @@ -2687,8 +2705,8 @@ async fn function_listing_uses_bounded_sql_and_scoped_version_cursors() { ); let seen = statements.lock().unwrap(); assert_eq!(seen.len(), 2); - assert!(seen[0].contains("limit = 2")); - assert!(seen[1].contains("after_version = '1'")); + assert!(seen[0].starts_with("SHOW FUNCTIONS HISTORY WITH (limit = 3,")); + assert!(seen[1].contains("after_version = '2'")); shutdown.send(()).unwrap(); }