Writing Stored Procedures in Snowpark (Java)

With this feature, you can write a stored procedure in Java. You can use the Snowpark library within your stored procedure to perform queries, updates, and other work on tables in Snowflake.

This topic explains how to write a stored procedure in Java.

In this Topic:

Introduction

With Snowpark Stored Procedures, you can build and run your data pipeline within Snowflake, using a Snowflake warehouse as the compute framework. For the code for your data pipeline, you use the Snowpark API for Java to write stored procedures. To schedule the execution of these stored procedures, you use tasks.

Note

To both create and call an anonymous procedure, use CALL (with Anonymous Procedure). Creating and calling an anonymous procedure does not require a role with CREATE PROCEDURE schema privileges.

Limitations

Snowpark Stored Procedures have the following limitations:

  • Concurrency is not supported. For example, from within your code, you cannot submit queries from multiple threads. Code that concurrently issues multiple queries will produce an error.

  • If you are executing your stored procedure from a task, you must specify a warehouse when creating the task. (You cannot use Snowflake-managed compute resources to run the task.)

  • Reading and writing files from stored procedure handler code in Java is not yet fully supported. Reading and writing files may result in unstable behavior. This includes receiving an InputStream as an argument and using methods available from the FileOperation class (which you typically access via the Session.file method), including put and get.

  • Keep in mind the following limitations for using some Snowpark APIs in your stored procedure.

    • When you use APIs that execute PUT and GET commands (including Session.sql("PUT ...") and Session.sql("GET ...")), you may write only to the /tmp directory in the memory-backed file system provided for the query calling the procedure.

    • Do not use APIs for asynchronous actions.

    • Do not use APIs that create new sessions (for example, Session.builder().configs(...).create()).

    • Using session.jdbcConnection (and the connection returned from it) is not supported because it may result in unsafe behavior.

Prerequisites

You must use version 1.3.0 or a more recent version of the Snowpark library.

If you are writing a pre-compiled stored procedure, you must compile your classes to run in Java version 11.x.

Setting Up Your Development Environment for Snowpark

Next, set up your development environment to use the Snowpark library. See Setting Up Your Development Environment for Snowpark Java.

Choosing to Create an In-Line or Pre-Compiled Stored Procedure

As is the case with Java UDFs, you can either create an in-line stored procedure or a pre-compiled stored procedure.

  • In an in-line stored procedure, you write your Java code in the AS clause of the CREATE PROCEDURE statement. For example:

    CREATE OR REPLACE PROCEDURE MYPROC(fromTable STRING, toTable STRING, count INT)
      RETURNS STRING
      LANGUAGE JAVA
      RUNTIME_VERSION = '11'
      PACKAGES = ('com.snowflake:snowpark:latest')
      HANDLER = 'MyJavaClass.run'
      AS
      $$
        import com.snowflake.snowpark_java.*;
    
        public class MyJavaClass {
          public String run(Session session, String fromTable, String toTable, int count) {
            session.table(fromTable).limit(count).write().saveAsTable(toTable);
            return "SUCCESS";
          }
        }
      $$;
    

    Note

    For faster execution on repeated calls, you can set TARGET_PATH to the location of a JAR file in which Snowflake should save the compiled classes. See Creating the Stored Procedure.

  • In a pre-compiled stored procedure, you write your Java code in a .java source file.

    For example:

    import com.snowflake.snowpark_java.*;
    
    public class MyJavaClass {
      public String run(Session session, String fromTable, String toTable, int count) {
        session.table(fromTable).limit(count).write().saveAsTable(toTable);
        return "SUCCESS";
      }
    }
    

    You then compile your code, package the classes in a JAR file, upload the JAR file to a stage, and execute the CREATE PROCEDURE command, pointing to the JAR file on the stage. For example:

    CREATE OR REPLACE PROCEDURE MYPROC(value INT, fromTable STRING, toTable STRING, count INT)
      RETURNS INT
      LANGUAGE JAVA
      RUNTIME_VERSION = '11'
      PACKAGES = ('com.snowflake:snowpark:latest')
      IMPORTS = ('@mystage/MyCompiledJavaCode.jar')
      HANDLER = 'MyJavaClass.run';
    

Writing the Java Code for the Stored Procedure

For the code for your stored procedure, you must write a Java method. The following sections provide guidelines for writing your code:

Planning to Write Your Stored Procedure

The Java code for your stored procedure has the same constraints as the code for a Java UDF. When planning to write your stored procedures, you should take those constraints into consideration.

Limit the Amount of Memory Consumed

As is the case with Java UDFs, Snowflake places limits on a method in terms of the amount of memory needed.

In your method, you should avoid consuming too much memory.

Write Thread-Safe Code

Similarly, as is the case with Java UDFs, you should make sure that your method is thread safe.

Understand the Security Restrictions

Your method runs within a restricted engine, so you should follow the same rules as documented for Java UDFs.

Decide on Using Owner’s Rights or Caller’s Rights

In addition, when planning to write your stored procedure, consider whether you want the stored procedure to run with caller’s rights or owner’s rights.

Keep in Mind the Timeout Behavior for Stored Procedures

Stored procedure execution will time out unless the timer is reset by the code’s activity. In particular, the timeout timer is reset by the code’s interactions with data, including file operations, queries, and iterating through a result set.

Writing the Class

The method that you define should be part of a class.

When writing the class, note the following:

  • The class and method must not be protected or private.

  • If the method is not static and you want to define a constructor, define a zero-argument constructor for the class. Snowflake invokes this zero-argument constructor at initialization time to create an instance of your class.

  • You can define different methods for different stored procedures in the same class.

Writing the Method

When writing the method for the stored procedure, note the following:

  • Specify the Snowpark Session object as the first argument of your method.

    When you call your stored procedure, Snowflake automatically creates a Session object and passes it to your stored procedure. (You cannot create the Session object yourself.)

  • For the rest of the arguments and for the return value, use the Java types that correspond to Snowflake data types.

  • Your method must return a value. For stored procedures in Java, a return value is required.

Accessing Data in Snowflake from Your Stored Procedure

To access data in Snowflake, use the Snowpark library APIs.

When handling a call to your Java stored procedure, Snowflake creates a Snowpark Session object and passes the object to the method for your stored procedure.

As is the case with stored procedures in other languages, the context for the session (e.g. the privileges, current database and schema, etc.) is determined by whether the stored procedure runs with caller’s rights or owner’s rights. For details, see Accessing and Setting the Session State.

You can use this Session object to call APIs in the Snowpark library. For example, you can create a DataFrame for a table or execute an SQL statement.

See the Snowpark Developer Guide for Java for more information.

Note

For information about limitations, including limitations on accessing data, see Limitations.

The following is an example of a Java method that copies a specified number of rows from one table to another table. The method takes the following arguments:

  • A Snowpark Session object

  • The name of the table to copy the rows from

  • The name of the table to save the rows to

  • The number of rows to copy

The method in this example returns a string.

import com.snowflake.snowpark_java.*;

public class MyClass
{
  public String myMethod(Session session, String fromTable, String toTable, int count)
  {
    session.table(fromTable).limit(count).write().saveAsTable(toTable);
    return "Success";
  }
}

Accessing Other Classes and Resource Files

If your code depends on classes defined outside of the stored procedure (e.g. classes in a separate JAR file) or resource files, you must upload those files to a stage so that the files will be available when the stored procedure executes.

Later, when executing the CREATE PROCEDURE statement, use the IMPORTS clause to point to these files.

Preparing a Pre-compiled Stored Procedure

If you plan to create a pre-compiled stored procedure (rather than an in-line stored procedure), you must compile and package your classes in a JAR file, and you must upload the JAR file to a stage.

Compiling and Packaging Your Java Code

To make it easier to set up your stored procedure, build a JAR file that contains all of the dependencies needed for your stored procedure. Later, you’ll need to upload the JAR file to a stage and point to the JAR file from your CREATE PROCEDURE statement. This process is simpler if you have fewer JAR files to upload and point to.

The next sections provide some tips on creating a JAR file that contains all of the dependencies:

Using Maven to Build a JAR File With Dependencies

If you are using Maven to build and package your code, you can use the Maven Assembly Plugin to create a JAR file that contains all of the dependencies.

  1. In the directory for your project (e.g. hello-snowpark/), create a subdirectory named assembly/.

  2. In that directory, create an assembly descriptor file that specifies that you want to include dependencies in your JAR file.

    For an example, see jar-with-dependencies.

  3. In the assembly descriptor, add a <dependencySet> element that excludes the Snowpark library from your JAR file.

    For example:

    <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
      <id>jar-with-dependencies</id>
      <formats>
         <format>jar</format>
      </formats>
      <includeBaseDirectory>false</includeBaseDirectory>
      <dependencySets>
        <dependencySet>
          <outputDirectory>/</outputDirectory>
          <useProjectArtifact>false</useProjectArtifact>
          <unpack>true</unpack>
          <scope>provided</scope>
          <excludes>
            <exclude>com.snowflake:snowpark</exclude>
          </excludes>
        </dependencySet>
      </dependencySets>
    </assembly>
    

    For information about the elements in an assembly descriptor, see Assembly Descriptor Format.

  4. In your pom.xml file, under the <project> » <build> » <plugins>, add a <plugin> element for the Maven Assembly Plugin.

    In addition, under <configuration> » <descriptors>, add a <descriptor> that points to the assembly descriptor file that you created in the previous steps.

    For example:

    <project>
      [...]
      <build>
        [...]
        <plugins>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.3.0</version>
            <configuration>
              <descriptors>
                <descriptor>src/assembly/jar-with-dependencies.xml</descriptor>
              </descriptors>
            </configuration>
            [...]
          </plugin>
          [...]
        </plugins>
        [...]
      </build>
      [...]
    </project>
    

Using Other Tools to Build a JAR File With Dependencies

If you are not using Maven, see the documentation for your build tool for instructions on building a JAR file with all of the dependencies.

For example, if you are using an IntelliJ IDEA project, see the instructions on setting up an artifact configuration.

Uploading Files to a Stage

Next, upload the files required for your stored procedure to a stage:

  1. Choose a stage for your files.

    You can use an external or named internal stage. If you plan to use the PUT command to upload the files, use a named internal stage. (The PUT command does not support uploading files to external stages.)

    You can use an existing stage, or you can create a new stage by executing CREATE STAGE. For example, the following command creates a new internal stage named mystage:

    CREATE STAGE mystage;
    

    Note

    The owner of the stored procedure must have the READ privilege to the stage.

  2. Use the PUT command to upload the following files to that stage:

    • The JAR file containing your compiled Java code.

    • Any other files that your stored procedure depends on.

    For example:

    PUT file:///Users/MyUserName/myjar.jar
            @mystage
            AUTO_COMPRESS = FALSE
            OVERWRITE = TRUE
            ;
    

    Note

    If you omit AUTO_COMPRESS = FALSE, the PUT command automatically compresses the file. The name of the compressed file on the stage will be myjar.jar.gz. Later, when you execute the CREATE PROCEDURE command, you will need to specify the filename with this .gz extension in the IMPORTS clause.

Note that if you delete or rename the JAR file containing your compiled Java code, you can no longer call the stored procedure.

If you need to update your JAR file, then:

  • Update it while no calls to the stored procedure can be made.

  • Add the clause OVERWRITE=TRUE to the PUT command.

Creating the Stored Procedure

To create the stored procedure, execute the CREATE PROCEDURE statement.

Note

To both create and call an anonymous procedure, use CALL (with Anonymous Procedure). Creating and calling an anonymous procedure does not require a role with CREATE PROCEDURE schema privileges.

Set the CREATE PROCEDURE parameters as listed in the table below.

Parameter

Description

[ arg_name arg_data_type [, ... ] ]

  • Omit the argument for the Snowpark Session object. As mentioned earlier, this is not a formal parameter that you specify in CREATE PROCEDURE or CALL. When you call your stored procedure, Snowflake creates a Session object and passes it to your stored procedure.

  • For the data types of the rest of the arguments, use the Snowflake data types that correspond to the Java types of the arguments in your method.

RETURNS result_data_type

Specify RETURNS with the Snowflake data type of your return value.

LANGUAGE JAVA

You must specify this to indicate that your stored procedure code is written in Java.

RUNTIME_VERSION = '11'

You must specify this to indicate that your stored procedure uses Java 11.

PACKAGES = ( 'package_name' )

In this list, include the package for the version of the Snowpark library that you want to use.

Specify the fully qualified package name for the Snowpark library in the following format:

com.snowflake:snowpark:<version>

For example:

  • To use the latest version of Snowpark, use:

    PACKAGES = ('com.snowflake:snowpark:latest')
    
  • To use the 1.6.2 version of Snowpark, use:

    PACKAGES = ('com.snowflake:snowpark:1.6.2')

Note

When creating a new stored procedure, specify the version 1.3.0 or later.

For the list of supported packages and versions, query the INFORMATION_SCHEMA.PACKAGES view for rows with LANGUAGE = 'java'. For example:

select * from information_schema.packages where language = 'java';

IMPORTS = ( 'file' [, 'file' ... ] )

If your stored procedure depends on any JAR files that you uploaded to a stage location, include those files in this list.

If you are writing an in-line stored procedure, you can omit this clause, unless your code depends on classes defined outside the stored procedure or resource files.

If you are writing a pre-compiled stored procedure, you must also include the JAR file containing the definition of the stored procedure.

HANDLER = 'method_name'

Set this to the fully qualified name of your Java method.

TARGET_PATH = 'jar_file'

If you are writing an inline stored procedure and you do not want Snowflake to recompile your code on every call, you can set this to the JAR file that Snowflake should create for your compiled code.

This must be a path on a stage for which you have WRITE privileges.

EXECUTE AS CALLER

If you planned to set up the stored procedure to use caller’s rights, add this parameter.

Otherwise, if you want to use owner’s rights, omit this parameter.

The following examples create stored procedures in Java.

Example 1: In-line stored procedure:

CREATE OR REPLACE PROCEDURE myProc(fromTable STRING, toTable STRING, count INT)
RETURNS STRING
LANGUAGE JAVA
RUNTIME_VERSION = '11'
PACKAGES = ('com.snowflake:snowpark:latest')
HANDLER = 'MyClass.myMethod'
AS
$$
  import com.snowflake.snowpark_java.*;

  public class MyClass
  {
    public String myMethod(Session session, String fromTable, String toTable, int count)
    {
      session.table(fromTable).limit(count).write().saveAsTable(toTable);
      return "Success";
    }
  }
$$;

Example 2: Pre-compiled stored procedure that uses compiled code in the JAR file myjar.jar on the internal stage mystage:

CREATE OR REPLACE PROCEDURE myProc(fromTable STRING, toTable STRING, count INT)
RETURNS STRING
LANGUAGE JAVA
RUNTIME_VERSION = '11'
PACKAGES = ('com.snowflake:snowpark:latest')
IMPORTS = ('@mystage/myjar.jar')
HANDLER = 'MyClass.myMethod';

Calling Your Stored Procedure

In order for a user to call a stored procedure, the user’s role must have the USAGE privilege for the stored procedure.

Once you have the privileges to call the stored procedure, you can use the CALL statement to call the stored procedure. For example:

CALL myProc('table_a', 'table_b', 5);
Back to top