@@ -1733,6 +1733,87 @@ def test_reset(self):
1733
1733
self .validate_identity ("RESET LOCAL threads" , check_command_warning = True )
1734
1734
self .validate_identity ("RESET SESSION default_collation" , check_command_warning = True )
1735
1735
1736
+ def test_install (self ):
1737
+ # Test basic INSTALL command
1738
+ self .validate_identity ("INSTALL httpfs" )
1739
+ self .validate_identity ("INSTALL spatial" )
1740
+
1741
+ # Test INSTALL with string literals (file paths)
1742
+ self .validate_identity ("INSTALL 'path/to/extension.duckdb_extension'" )
1743
+ self .validate_identity ("INSTALL '/absolute/path/to/extension.duckdb_extension'" )
1744
+
1745
+ # Test INSTALL with FROM clause
1746
+ self .validate_identity ("INSTALL httpfs FROM community" )
1747
+ self .validate_identity ("INSTALL spatial FROM community" )
1748
+ self .validate_identity ("INSTALL h3 FROM community" )
1749
+ self .validate_identity ("INSTALL spatial FROM core_nightly" )
1750
+
1751
+ # Test INSTALL with FROM clause using string literals (URLs)
1752
+ self .validate_identity ("INSTALL spatial FROM 'http://nightly-extensions.duckdb.org'" )
1753
+ self .validate_identity ("INSTALL httpfs FROM 'https://extensions.duckdb.org'" )
1754
+
1755
+ # Test FORCE INSTALL command
1756
+ self .validate_identity ("FORCE INSTALL httpfs" )
1757
+ self .validate_identity ("FORCE INSTALL spatial" )
1758
+
1759
+ # Test FORCE INSTALL with FROM clause
1760
+ self .validate_identity ("FORCE INSTALL httpfs FROM community" )
1761
+ self .validate_identity ("FORCE INSTALL spatial FROM community" )
1762
+ self .validate_identity ("FORCE INSTALL spatial FROM 'http://nightly-extensions.duckdb.org'" )
1763
+
1764
+ # Test parsing and generation of Install expression
1765
+ install_expr = parse_one ("INSTALL httpfs" , dialect = "duckdb" )
1766
+ self .assertIsInstance (install_expr , exp .Install )
1767
+ self .assertEqual (install_expr .args ["this" ].name , "httpfs" )
1768
+ self .assertIsNone (install_expr .args .get ("from_" ))
1769
+ self .assertFalse (install_expr .args .get ("force" , False ))
1770
+
1771
+ # Test parsing with FROM clause
1772
+ install_with_from = parse_one ("INSTALL spatial FROM community" , dialect = "duckdb" )
1773
+ self .assertIsInstance (install_with_from , exp .Install )
1774
+ self .assertEqual (install_with_from .args ["this" ].name , "spatial" )
1775
+ self .assertEqual (install_with_from .args ["from_" ].name , "community" )
1776
+ self .assertFalse (install_with_from .args .get ("force" , False ))
1777
+
1778
+ # Test parsing FORCE INSTALL
1779
+ force_install = parse_one ("FORCE INSTALL httpfs" , dialect = "duckdb" )
1780
+ self .assertIsInstance (force_install , exp .Install )
1781
+ self .assertEqual (force_install .args ["this" ].name , "httpfs" )
1782
+ self .assertIsNone (force_install .args .get ("from_" ))
1783
+ self .assertTrue (force_install .args .get ("force" , False ))
1784
+
1785
+ # Test FORCE INSTALL with FROM
1786
+ force_install_from = parse_one ("FORCE INSTALL spatial FROM community" , dialect = "duckdb" )
1787
+ self .assertIsInstance (force_install_from , exp .Install )
1788
+ self .assertEqual (force_install_from .args ["this" ].name , "spatial" )
1789
+ self .assertEqual (force_install_from .args ["from_" ].name , "community" )
1790
+ self .assertTrue (force_install_from .args .get ("force" , False ))
1791
+
1792
+ # Test string path parsing
1793
+ install_path = parse_one ("INSTALL 'path/to/ext.duckdb_extension'" , dialect = "duckdb" )
1794
+ self .assertIsInstance (install_path , exp .Install )
1795
+ self .assertEqual (install_path .args ["this" ].this , "path/to/ext.duckdb_extension" )
1796
+
1797
+ # Test cross-dialect compatibility (should not parse as Install in other dialects)
1798
+ # In other dialects, INSTALL should not be recognized as a special command
1799
+ try :
1800
+ non_duckdb_result = parse_one ("INSTALL httpfs" , dialect = "postgres" )
1801
+ # Should parse as something else, not Install
1802
+ self .assertNotIsInstance (non_duckdb_result , exp .Install )
1803
+ except Exception :
1804
+ # Or fail to parse entirely, which is also acceptable
1805
+ pass
1806
+
1807
+ # Test error cases
1808
+ with self .assertRaises (ParseError ):
1809
+ parse_one ("INSTALL" , dialect = "duckdb" ) # Missing extension name
1810
+
1811
+ with self .assertRaises (ParseError ):
1812
+ parse_one ("FORCE" , dialect = "duckdb" ) # FORCE without INSTALL
1813
+
1814
+ with self .assertRaises (ParseError ):
1815
+ parse_one ("INSTALL httpfs FROM" , dialect = "duckdb" ) # FROM without repository
1816
+
1736
1817
def test_map_struct (self ):
1737
1818
self .validate_identity ("MAP {1: 'a', 2: 'b'}" )
1738
1819
self .validate_identity ("MAP {'1': 'a', '2': 'b'}" )
0 commit comments