> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lancedb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema and Data Evolution

> Learn how to manage table schemas in LanceDB, including adding, altering, and dropping columns.

export const RsAlterVectorColumn = "let old_dim = 384;\nlet new_dim = 1024;\nlet vector_schema = Arc::new(Schema::new(vec![\n    Field::new(\"id\", DataType::Int64, false),\n    Field::new(\n        \"embedding\",\n        DataType::FixedSizeList(\n            Arc::new(Field::new(\"item\", DataType::Float32, true)),\n            old_dim,\n        ),\n        true,\n    ),\n]));\nlet vector_batch = RecordBatch::try_new(\n    vector_schema.clone(),\n    vec![\n        Arc::new(Int64Array::from(vec![1])),\n        Arc::new(\n            FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(\n                vec![Some(vec![Some(0.1_f32); old_dim as usize])],\n                old_dim,\n            ),\n        ),\n    ],\n)\n.unwrap();\nlet vector_reader =\n    RecordBatchIterator::new(vec![Ok(vector_batch)].into_iter(), vector_schema.clone());\nlet vector_table = db\n    .create_table(\"vector_alter_example\", vector_reader)\n    .mode(CreateTableMode::Overwrite)\n    .execute()\n    .await\n    .unwrap();\n\n// Changing FixedSizeList dimensions (384 -> 1024) is not supported via alter_columns.\n// Use add_columns + drop_columns + alter_columns(rename) to replace the column.\nvector_table\n    .add_columns(\n        NewColumnTransform::SqlExpressions(vec![(\n            \"embedding_v2\".to_string(),\n            format!(\"arrow_cast(NULL, 'FixedSizeList({}, Float32)')\", new_dim),\n        )]),\n        None,\n    )\n    .await\n    .unwrap();\nvector_table.drop_columns(&[\"embedding\"]).await.unwrap();\nvector_table\n    .alter_columns(&[ColumnAlteration::new(\"embedding_v2\".to_string())\n        .rename(\"embedding\".to_string())])\n    .await\n    .unwrap();\n";

export const TsAlterVectorColumn = "const oldDim = 384;\nconst newDim = 1024;\nconst vectorSchema = new arrow.Schema([\n  new arrow.Field(\"id\", new arrow.Int64()),\n  new arrow.Field(\n    \"embedding\",\n    new arrow.FixedSizeList(\n      oldDim,\n      new arrow.Field(\"item\", new arrow.Float16(), true),\n    ),\n    true,\n  ),\n]);\nconst vectorData = lancedb.makeArrowTable(\n  [{ id: 1, embedding: Array.from({ length: oldDim }, () => Math.random()) }],\n  { schema: vectorSchema },\n);\nconst vectorTable = await db.createTable(\"vector_alter_example\", vectorData, {\n  mode: \"overwrite\",\n});\n\n// Changing FixedSizeList dimensions (384 -> 1024) is not supported via alterColumns.\n// Use addColumns + dropColumns + alterColumns(rename) to replace the column.\nawait vectorTable.addColumns([\n  {\n    name: \"embedding_v2\",\n    valueSql: `arrow_cast(NULL, 'FixedSizeList(${newDim}, Float16)')`,\n  },\n]);\nawait vectorTable.dropColumns([\"embedding\"]);\nawait vectorTable.alterColumns([{ path: \"embedding_v2\", rename: \"embedding\" }]);\n";

export const AlterVectorColumn = "vector_dim = 768  # Your embedding dimension\ntable_name = \"vector_alter_example\"\ndb = tmp_db\ndata = [\n    {\n        \"id\": 1,\n        \"embedding\": np.random.random(vector_dim).tolist(),\n    },\n]\ntable = db.create_table(table_name, data, mode=\"overwrite\")\n\ntable.alter_columns(\n    dict(path=\"embedding\", data_type=pa.list_(pa.float32(), vector_dim))\n)\n";

export const RsDropColumnsMultiple = "// Remove the second temporary column\nschema_drop_table.drop_columns(&[\"temp_col2\"]).await.unwrap();\n";

export const TsDropColumnsMultiple = "// Remove the second temporary column\nawait schemaDropTable.dropColumns([\"temp_col2\"]);\n";

export const DropColumnsMultiple = "# Remove the second temporary column\ntable.drop_columns([\"temp_col2\"])\n";

export const RsDropColumnsSingle = "// Remove the first temporary column\nschema_drop_table.drop_columns(&[\"temp_col1\"]).await.unwrap();\n";

export const TsDropColumnsSingle = "// Remove the first temporary column\nawait schemaDropTable.dropColumns([\"temp_col1\"]);\n";

export const DropColumnsSingle = "# Remove the first temporary column\ntable.drop_columns([\"temp_col1\"])\n";

export const RsSchemaDropSetup = "let schema_drop_schema = Arc::new(Schema::new(vec![\n    Field::new(\"id\", DataType::Int64, false),\n    Field::new(\"name\", DataType::Utf8, false),\n    Field::new(\"price\", DataType::Float64, false),\n    Field::new(\"temp_col1\", DataType::Utf8, false),\n    Field::new(\"temp_col2\", DataType::Int32, false),\n    Field::new(\n        \"vector\",\n        DataType::FixedSizeList(Arc::new(Field::new(\"item\", DataType::Float32, true)), 128),\n        false,\n    ),\n]));\nlet schema_drop_batch = RecordBatch::try_new(\n    schema_drop_schema.clone(),\n    vec![\n        Arc::new(Int64Array::from(vec![1, 2, 3])),\n        Arc::new(StringArray::from(vec![\"Laptop\", \"Smartphone\", \"Headphones\"])),\n        Arc::new(Float64Array::from(vec![1200.0, 800.0, 150.0])),\n        Arc::new(StringArray::from(vec![\"X\", \"Y\", \"Z\"])),\n        Arc::new(Int32Array::from(vec![100, 200, 300])),\n        Arc::new(\n            FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(\n                vec![\n                    Some(vec![Some(0.1_f32); 128]),\n                    Some(vec![Some(0.2_f32); 128]),\n                    Some(vec![Some(0.3_f32); 128]),\n                ],\n                128,\n            ),\n        ),\n    ],\n)\n.unwrap();\nlet schema_drop_reader = RecordBatchIterator::new(\n    vec![Ok(schema_drop_batch)].into_iter(),\n    schema_drop_schema.clone(),\n);\nlet schema_drop_table = db\n    .create_table(\"schema_evolution_drop_example\", schema_drop_reader)\n    .mode(CreateTableMode::Overwrite)\n    .execute()\n    .await\n    .unwrap();\n";

export const TsSchemaDropSetup = "const schemaDropData = [\n  {\n    id: 1,\n    name: \"Laptop\",\n    price: 1200.0,\n    temp_col1: \"X\",\n    temp_col2: 100,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n  {\n    id: 2,\n    name: \"Smartphone\",\n    price: 800.0,\n    temp_col1: \"Y\",\n    temp_col2: 200,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n  {\n    id: 3,\n    name: \"Headphones\",\n    price: 150.0,\n    temp_col1: \"Z\",\n    temp_col2: 300,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n];\nconst schemaDropTable = await db.createTable(\n  \"schema_evolution_drop_example\",\n  schemaDropData,\n  { mode: \"overwrite\" },\n);\n";

export const SchemaDropSetup = "if data is None:\n    data = [\n        {\n            \"id\": 1,\n            \"name\": \"Laptop\",\n            \"price\": 1200.00,\n            \"temp_col1\": \"X\",\n            \"temp_col2\": 100,\n            \"vector\": np.random.random(128).tolist(),\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Smartphone\",\n            \"price\": 800.00,\n            \"temp_col1\": \"Y\",\n            \"temp_col2\": 200,\n            \"vector\": np.random.random(128).tolist(),\n        },\n        {\n            \"id\": 3,\n            \"name\": \"Headphones\",\n            \"price\": 150.00,\n            \"temp_col1\": \"Z\",\n            \"temp_col2\": 300,\n            \"vector\": np.random.random(128).tolist(),\n        },\n    ]\ntable = tmp_db.create_table(\"schema_evolution_drop_example\", data, mode=\"overwrite\")\n";

export const RsAlterColumnsWithExpression = "// For custom transforms, create a new column from a SQL expression.\nlet expression_schema = Arc::new(Schema::new(vec![\n    Field::new(\"id\", DataType::Int64, false),\n    Field::new(\"price_text\", DataType::Utf8, false),\n]));\nlet expression_batch = RecordBatch::try_new(\n    expression_schema.clone(),\n    vec![\n        Arc::new(Int64Array::from(vec![1])),\n        Arc::new(StringArray::from(vec![\"$100\"])),\n    ],\n)\n.unwrap();\nlet expression_reader = RecordBatchIterator::new(\n    vec![Ok(expression_batch)].into_iter(),\n    expression_schema.clone(),\n);\nlet expression_table = db\n    .create_table(\"schema_evolution_expression_example\", expression_reader)\n    .mode(CreateTableMode::Overwrite)\n    .execute()\n    .await\n    .unwrap();\n\nexpression_table\n    .add_columns(\n        NewColumnTransform::SqlExpressions(vec![(\n            \"price_numeric\".to_string(),\n            \"cast(replace(price_text, '$', '') as int)\".to_string(),\n        )]),\n        None,\n    )\n    .await\n    .unwrap();\nexpression_table.drop_columns(&[\"price_text\"]).await.unwrap();\nexpression_table\n    .alter_columns(&[ColumnAlteration::new(\"price_numeric\".to_string())\n        .rename(\"price\".to_string())])\n    .await\n    .unwrap();\n";

export const RsAlterColumnsMultiple = "// Rename, change type, and make nullable in one operation\nschema_alter_table\n    .alter_columns(&[ColumnAlteration::new(\"sale_price\".to_string())\n        .rename(\"final_price\".to_string())\n        .cast_to(DataType::Float64)\n        .set_nullable(true)])\n    .await\n    .unwrap();\n";

export const TsAlterColumnsWithExpression = "// For custom transforms, create a new column from a SQL expression.\nconst expressionTable = await db.createTable(\n  \"schema_evolution_expression_example\",\n  [{ id: 1, price_text: \"$100\" }],\n  { mode: \"overwrite\" },\n);\n\nawait expressionTable.addColumns([\n  {\n    name: \"price_numeric\",\n    valueSql: \"cast(replace(price_text, '$', '') as int)\",\n  },\n]);\nawait expressionTable.dropColumns([\"price_text\"]);\nawait expressionTable.alterColumns([\n  { path: \"price_numeric\", rename: \"price\" },\n]);\n";

export const AlterColumnsWithExpression = "# For custom transforms, create a new column from a SQL expression.\nexpression_table = tmp_db.create_table(\n    \"schema_evolution_expression_example\",\n    [{\"id\": 1, \"price_text\": \"$100\"}],\n    mode=\"overwrite\",\n)\n\nexpression_table.add_columns(\n    {\"price_numeric\": \"cast(replace(price_text, '$', '') as int)\"}\n)\nexpression_table.drop_columns([\"price_text\"])\nexpression_table.alter_columns({\"path\": \"price_numeric\", \"rename\": \"price\"})\n";

export const TsAlterColumnsMultiple = "// Rename, change type, and make nullable in one operation\nawait schemaAlterTable.alterColumns([\n  {\n    path: \"sale_price\",\n    rename: \"final_price\",\n    dataType: new arrow.Float64(),\n    nullable: true,\n  },\n]);\n";

export const AlterColumnsMultiple = "# Rename, change type, and make nullable in one operation\ntable.alter_columns(\n    {\n        \"path\": \"sale_price\",\n        \"rename\": \"final_price\",\n        \"data_type\": pa.float64(),\n        \"nullable\": True,\n    }\n)\n";

export const RsAlterColumnsNullable = "// Make the name column nullable\nschema_alter_table\n    .alter_columns(&[ColumnAlteration::new(\"name\".to_string()).set_nullable(true)])\n    .await\n    .unwrap();\n";

export const TsAlterColumnsNullable = "// Make the name column nullable\nawait schemaAlterTable.alterColumns([{ path: \"name\", nullable: true }]);\n";

export const AlterColumnsNullable = "# Make the name column nullable\ntable.alter_columns({\"path\": \"name\", \"nullable\": True})\n";

export const RsAlterColumnsDataType = "// Change price from int32 to int64 for larger numbers\nschema_alter_table\n    .alter_columns(&[ColumnAlteration::new(\"price\".to_string()).cast_to(DataType::Int64)])\n    .await\n    .unwrap();\n";

export const TsAlterColumnsDataType = "// Change price from int32 to int64 for larger numbers\nawait schemaAlterTable.alterColumns([\n  { path: \"price\", dataType: new arrow.Int64() },\n]);\n";

export const AlterColumnsDataType = "# Change price from int32 to int64 for larger numbers\ntable.alter_columns({\"path\": \"price\", \"data_type\": pa.int64()})\n";

export const RsAlterColumnsRename = "// Rename discount_price to sale_price\nschema_alter_table\n    .alter_columns(&[ColumnAlteration::new(\"discount_price\".to_string())\n        .rename(\"sale_price\".to_string())])\n    .await\n    .unwrap();\n";

export const TsAlterColumnsRename = "// Rename discount_price to sale_price\nawait schemaAlterTable.alterColumns([\n  { path: \"discount_price\", rename: \"sale_price\" },\n]);\n";

export const AlterColumnsRename = "# Rename discount_price to sale_price\ntable.alter_columns({\"path\": \"discount_price\", \"rename\": \"sale_price\"})\n";

export const RsSchemaAlterSetup = "let schema_alter_schema = Arc::new(Schema::new(vec![\n    Field::new(\"id\", DataType::Int64, false),\n    Field::new(\"name\", DataType::Utf8, false),\n    Field::new(\"price\", DataType::Int32, false),\n    Field::new(\"discount_price\", DataType::Float64, false),\n    Field::new(\n        \"vector\",\n        DataType::FixedSizeList(Arc::new(Field::new(\"item\", DataType::Float32, true)), 128),\n        false,\n    ),\n]));\nlet schema_alter_batch = RecordBatch::try_new(\n    schema_alter_schema.clone(),\n    vec![\n        Arc::new(Int64Array::from(vec![1, 2])),\n        Arc::new(StringArray::from(vec![\"Laptop\", \"Smartphone\"])),\n        Arc::new(Int32Array::from(vec![1200, 800])),\n        Arc::new(Float64Array::from(vec![1080.0, 720.0])),\n        Arc::new(\n            FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(\n                vec![Some(vec![Some(0.1_f32); 128]), Some(vec![Some(0.2_f32); 128])],\n                128,\n            ),\n        ),\n    ],\n)\n.unwrap();\nlet schema_alter_reader = RecordBatchIterator::new(\n    vec![Ok(schema_alter_batch)].into_iter(),\n    schema_alter_schema.clone(),\n);\nlet schema_alter_table = db\n    .create_table(\"schema_evolution_alter_example\", schema_alter_reader)\n    .mode(CreateTableMode::Overwrite)\n    .execute()\n    .await\n    .unwrap();\n";

export const TsSchemaAlterSetup = "const schemaAlter = new arrow.Schema([\n  new arrow.Field(\"id\", new arrow.Int64()),\n  new arrow.Field(\"name\", new arrow.Utf8()),\n  new arrow.Field(\"price\", new arrow.Int32()),\n  new arrow.Field(\"discount_price\", new arrow.Float64()),\n  new arrow.Field(\n    \"vector\",\n    new arrow.FixedSizeList(\n      128,\n      new arrow.Field(\"item\", new arrow.Float32(), true),\n    ),\n  ),\n]);\nconst schemaAlterData = lancedb.makeArrowTable(\n  [\n    {\n      id: 1,\n      name: \"Laptop\",\n      price: 1200,\n      discount_price: 1080.0,\n      vector: Array.from({ length: 128 }, () => Math.random()),\n    },\n    {\n      id: 2,\n      name: \"Smartphone\",\n      price: 800,\n      discount_price: 720.0,\n      vector: Array.from({ length: 128 }, () => Math.random()),\n    },\n  ],\n  { schema: schemaAlter },\n);\nconst schemaAlterTable = await db.createTable(\n  \"schema_evolution_alter_example\",\n  schemaAlterData,\n  { mode: \"overwrite\" },\n);\n";

export const SchemaAlterSetup = "table_name = \"schema_evolution_alter_example\"\nif data is None:\n    data = [\n        {\n            \"id\": 1,\n            \"name\": \"Laptop\",\n            \"price\": 1200,\n            \"discount_price\": 1080.0,\n            \"vector\": np.random.random(128).tolist(),\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Smartphone\",\n            \"price\": 800,\n            \"discount_price\": 720.0,\n            \"vector\": np.random.random(128).tolist(),\n        },\n    ]\nschema = pa.schema(\n    {\n        \"id\": pa.int64(),\n        \"name\": pa.string(),\n        \"price\": pa.int32(),\n        \"discount_price\": pa.float64(),\n        \"vector\": pa.list_(pa.float32(), 128),\n    }\n)\ntable = tmp_db.create_table(table_name, data, schema=schema, mode=\"overwrite\")\n";

export const RsAddColumnsNullable = "// Add a nullable timestamp column\nschema_add_table\n    .add_columns(\n        NewColumnTransform::SqlExpressions(vec![(\n            \"last_ordered\".to_string(),\n            \"cast(NULL as timestamp)\".to_string(),\n        )]),\n        None,\n    )\n    .await\n    .unwrap();\n";

export const TsAddColumnsNullable = "// Add a nullable timestamp column\nawait schemaAddTable.addColumns([\n  {\n    name: \"last_ordered\",\n    valueSql: \"cast(NULL as timestamp)\",\n  },\n]);\n";

export const AddColumnsNullable = "# Add a nullable timestamp column\ntable.add_columns({\"last_ordered\": \"cast(NULL as timestamp)\"})\n";

export const RsAddColumnsDefaultValues = "// Add a stock status column with default value\nschema_add_table\n    .add_columns(\n        NewColumnTransform::SqlExpressions(vec![(\n            \"in_stock\".to_string(),\n            \"cast(true as boolean)\".to_string(),\n        )]),\n        None,\n    )\n    .await\n    .unwrap();\n";

export const TsAddColumnsDefaultValues = "// Add a stock status column with default value\nawait schemaAddTable.addColumns([\n  {\n    name: \"in_stock\",\n    valueSql: \"cast(true as boolean)\",\n  },\n]);\n";

export const AddColumnsDefaultValues = "# Add a stock status column with default value\ntable.add_columns({\"in_stock\": \"cast(true as boolean)\"})\n";

export const RsAddColumnsCalculated = "// Add a discounted price column (10% discount)\nschema_add_table\n    .add_columns(\n        NewColumnTransform::SqlExpressions(vec![(\n            \"discounted_price\".to_string(),\n            \"cast((price * 0.9) as float)\".to_string(),\n        )]),\n        None,\n    )\n    .await\n    .unwrap();\n";

export const TsAddColumnsCalculated = "// Add a discounted price column (10% discount)\nawait schemaAddTable.addColumns([\n  {\n    name: \"discounted_price\",\n    valueSql: \"cast((price * 0.9) as float)\",\n  },\n]);\n";

export const AddColumnsCalculated = "# Add a discounted price column (10% discount)\ntable.add_columns({\"discounted_price\": \"cast((price * 0.9) as float)\"})\n";

export const RsSchemaAddSetup = "let schema_add_schema = Arc::new(Schema::new(vec![\n    Field::new(\"id\", DataType::Int64, false),\n    Field::new(\"name\", DataType::Utf8, false),\n    Field::new(\"price\", DataType::Float64, false),\n    Field::new(\n        \"vector\",\n        DataType::FixedSizeList(Arc::new(Field::new(\"item\", DataType::Float32, true)), 128),\n        false,\n    ),\n]));\nlet schema_add_batch = RecordBatch::try_new(\n    schema_add_schema.clone(),\n    vec![\n        Arc::new(Int64Array::from(vec![1, 2, 3])),\n        Arc::new(StringArray::from(vec![\"Laptop\", \"Smartphone\", \"Headphones\"])),\n        Arc::new(Float64Array::from(vec![1200.0, 800.0, 150.0])),\n        Arc::new(\n            FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(\n                vec![\n                    Some(vec![Some(0.1_f32); 128]),\n                    Some(vec![Some(0.2_f32); 128]),\n                    Some(vec![Some(0.3_f32); 128]),\n                ],\n                128,\n            ),\n        ),\n    ],\n)\n.unwrap();\nlet schema_add_reader = RecordBatchIterator::new(\n    vec![Ok(schema_add_batch)].into_iter(),\n    schema_add_schema.clone(),\n);\nlet schema_add_table = db\n    .create_table(\"schema_evolution_add_example\", schema_add_reader)\n    .mode(CreateTableMode::Overwrite)\n    .execute()\n    .await\n    .unwrap();\n";

export const TsSchemaAddSetup = "const schemaAddData = [\n  {\n    id: 1,\n    name: \"Laptop\",\n    price: 1200.0,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n  {\n    id: 2,\n    name: \"Smartphone\",\n    price: 800.0,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n  {\n    id: 3,\n    name: \"Headphones\",\n    price: 150.0,\n    vector: Array.from({ length: 128 }, () => Math.random()),\n  },\n];\nconst schemaAddTable = await db.createTable(\n  \"schema_evolution_add_example\",\n  schemaAddData,\n  { mode: \"overwrite\" },\n);\n";

export const SchemaAddSetup = "table_name = \"schema_evolution_add_example\"\nif data is None:\n    data = [\n        {\n            \"id\": 1,\n            \"name\": \"Laptop\",\n            \"price\": 1200.00,\n            \"vector\": np.random.random(128).tolist(),\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Smartphone\",\n            \"price\": 800.00,\n            \"vector\": np.random.random(128).tolist(),\n        },\n        {\n            \"id\": 3,\n            \"name\": \"Headphones\",\n            \"price\": 150.00,\n            \"vector\": np.random.random(128).tolist(),\n        },\n    ]\ntable = tmp_db.create_table(table_name, data, mode=\"overwrite\")\n";

Schema evolution enables non-breaking modifications to a database table's structure — such as adding columns, altering data types, or dropping fields — to adapt to evolving data requirements without service interruptions.
LanceDB supports ACID-compliant schema evolution through granular operations (add/alter/drop columns), allowing you to:

* Iterate Safely: Modify schemas in production with versioned datasets and backward compatibility
* Scale Seamlessly: Handle ML model iterations, regulatory changes, or feature additions
* Optimize Continuously: Remove unused fields or enforce new constraints without downtime

## Schema evolution operations

LanceDB supports three primary schema evolution operations:

1. **Adding new columns**: Extend your table with additional attributes
2. **Altering existing columns**: Change column names, data types, or nullability
3. **Dropping columns**: Remove unnecessary columns from your schema

<Tip title="Schema Evolution Performance">
  Schema evolution operations are applied immediately but do not typically require rewriting all data. However, data type changes may involve more substantial operations.
</Tip>

## Add new columns

You can add new columns to a table with the [`add_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.add_columns)
method in Python, [`addColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#addcolumns) in TypeScript/JavaScript, or `add_columns` in Rust.
New columns are populated based on SQL expressions you provide.

### Set up the example table

First, let's create a sample table with product data to demonstrate schema evolution:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {SchemaAddSetup}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsSchemaAddSetup}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsSchemaAddSetup}
  </CodeBlock>
</CodeGroup>

### Add derived columns

You can add new columns that are derived from existing data using SQL expressions:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AddColumnsCalculated}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAddColumnsCalculated}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAddColumnsCalculated}
  </CodeBlock>
</CodeGroup>

### Add columns with default values

Add boolean columns with default values for status tracking:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AddColumnsDefaultValues}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAddColumnsDefaultValues}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAddColumnsDefaultValues}
  </CodeBlock>
</CodeGroup>

### Add nullable columns

Add timestamp columns that can contain NULL values:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AddColumnsNullable}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAddColumnsNullable}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAddColumnsNullable}
  </CodeBlock>
</CodeGroup>

<Warning title="NULL Values in New Columns">
  When adding columns that should contain NULL values, be sure to cast the NULL to the appropriate type, e.g., `cast(NULL as timestamp)`.
</Warning>

## Alter existing columns

You can alter columns using the [`alter_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.alter_columns)
method in Python, [`alterColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#altercolumns) in TypeScript/JavaScript, or `alter_columns` in Rust. This allows you to:

* Rename a column
* Change a column's data type
* Modify nullability (whether a column can contain NULL values)

### Set up the example table

Create a table with a custom schema to demonstrate column alterations:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {SchemaAlterSetup}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsSchemaAlterSetup}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsSchemaAlterSetup}
  </CodeBlock>
</CodeGroup>

### Rename columns

Change column names to better reflect their purpose:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterColumnsRename}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterColumnsRename}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterColumnsRename}
  </CodeBlock>
</CodeGroup>

### Change data types

Convert column data types for better performance or compatibility:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterColumnsDataType}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterColumnsDataType}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterColumnsDataType}
  </CodeBlock>
</CodeGroup>

### Make columns nullable

You can alter columns to contain NULL values:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterColumnsNullable}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterColumnsNullable}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterColumnsNullable}
  </CodeBlock>
</CodeGroup>

### Multiple changes at once

Apply several alterations in a single operation:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterColumnsMultiple}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterColumnsMultiple}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterColumnsMultiple}
  </CodeBlock>
</CodeGroup>

### Expression-based type changes

For transformations that are not simple casts (for example, converting `"$100"` to an integer), use a SQL-expression column add, then drop and rename:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterColumnsWithExpression}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterColumnsWithExpression}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterColumnsWithExpression}
  </CodeBlock>
</CodeGroup>

### Alter embedding types and dimensions

It's quite common to need to change an embedding column's schema, in case a new model becomes available with a different embedding dimension.

* In Python, the example shows an in-place type update when the cast is compatible.
* In TypeScript and Rust, the example shows a dimension change (`384 -> 1024`), which cannot be cast in-place.

For dimension changes, use this 3-step pattern: add a new column with the target type, drop the old column, then rename the new column to the original name.

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {AlterVectorColumn}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsAlterVectorColumn}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsAlterVectorColumn}
  </CodeBlock>
</CodeGroup>

<Warning>
  **`FixedSizeList` Dimension Changes in TypeScript and Rust**

  `alterColumns` / `alter_columns` can cast between compatible types, but changing `FixedSizeList` dimensions (for example `384 -> 1024`) is not a compatible cast.
  For such cases, use `addColumns` / `add_columns` (with `arrow_cast`), then `dropColumns` / `drop_columns`, then rename the replacement column.
</Warning>

<Warning title="Data Type Changes">
  Changing data types requires rewriting the column data and may be resource-intensive for large tables. Renaming columns or changing nullability is more efficient as it only updates metadata.
</Warning>

## Drop columns

You can remove columns using the [`drop_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.drop_columns)
method in Python, [`dropColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#dropcolumns) in TypeScript/JavaScript, or `drop_columns` in Rust.

### Set Up the example table

Create a table with temporary columns that we'll remove:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {SchemaDropSetup}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsSchemaDropSetup}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsSchemaDropSetup}
  </CodeBlock>
</CodeGroup>

### Drop single columns

Remove individual columns that are no longer needed:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {DropColumnsSingle}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsDropColumnsSingle}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsDropColumnsSingle}
  </CodeBlock>
</CodeGroup>

### Drop multiple columns

Remove several columns at once for efficiency:

<CodeGroup>
  <CodeBlock filename="Python" language="Python" icon="python">
    {DropColumnsMultiple}
  </CodeBlock>

  <CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
    {TsDropColumnsMultiple}
  </CodeBlock>

  <CodeBlock filename="Rust" language="Rust" icon="rust">
    {RsDropColumnsMultiple}
  </CodeBlock>
</CodeGroup>

<Warning title="Irreversible Column Deletion">
  Dropping columns cannot be undone. Make sure you have backups or are certain before removing columns.
</Warning>
